Options validation source generator (#87587)
authorTarek Mahmoud Sayed <tarekms@microsoft.com>
Tue, 20 Jun 2023 21:56:21 +0000 (14:56 -0700)
committerGitHub <noreply@github.com>
Tue, 20 Jun 2023 21:56:21 +0000 (14:56 -0700)
78 files changed:
docs/project/list-of-diagnostics.md
src/libraries/Common/src/System/ThrowHelper.cs
src/libraries/Microsoft.Extensions.Options/gen/DiagDescriptors.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/DiagDescriptorsBase.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Emitter.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/EmitterBase.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Generator.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Microsoft.Extensions.Options.SourceGeneration.csproj [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Model/ValidatedMember.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Model/ValidatedModel.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Model/ValidationAttributeInfo.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Model/ValidatorType.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Parser.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/ParserUtilities.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Resources/Strings.resx [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.cs.xlf [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.de.xlf [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.es.xlf [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.fr.xlf [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.it.xlf [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ja.xlf [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ko.xlf [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.pl.xlf [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.pt-BR.xlf [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ru.xlf [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.tr.xlf [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.zh-Hans.xlf [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.zh-Hant.xlf [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/SymbolHolder.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/SymbolLoader.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/TypeDeclarationSyntaxReceiver.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/ref/Microsoft.Extensions.Options.cs
src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj
src/libraries/Microsoft.Extensions.Options/src/OptionsValidatorAttribute.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/src/ValidateEnumeratedItemsAttribute.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/src/ValidateObjectMembersAttribute.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Microsoft.Extensions.Options.SourceGeneration.Unit.Tests.csproj [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Resources/Strings.resx [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetCoreApp/Validators.g.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetFX/Validators.g.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/EmitterTests.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/EmptyReadOnlyList.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/EmptyReadonlyDictionary.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/TimeSpanAttribute.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/ValidationContextExtensions.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/CustomAttrTests.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/EnumerationTests.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/FieldTests.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/FunnyStringsTests.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/GenericsTests.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/MultiModelValidatorTests.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/NestedTests.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/NoNamespaceTests.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/OptionsValidationTests.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/RandomMembersTests.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/RecordTypesTests.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/RepeatedTypesTests.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/SelfValidationTests.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/Utils.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/ValueTypesTests.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Microsoft.Extensions.Options.SourceGeneration.Tests.csproj [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Resources/Strings.resx [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/CustomAttr.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Enumeration.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Fields.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/FileScopedNamespace.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/FunnyStrings.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Generics.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Models.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/MultiModelValidator.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Nested.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/NoNamespace.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RandomMembers.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RecordTypes.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RepeatedTypes.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/SelfValidation.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/ValueTypes.cs [new file with mode: 0644]

index 2d1eab1..c3732de 100644 (file)
@@ -235,7 +235,24 @@ The diagnostic id values reserved for .NET Libraries analyzer warnings are `SYSL
 |  __`SYSLIB1116`__ | *_`SYSLIB1100`-`SYSLIB1118` reserved for Microsoft.Extensions.Configuration.Binder.SourceGeneration.* |
 |  __`SYSLIB1117`__ | *_`SYSLIB1100`-`SYSLIB1118` reserved for Microsoft.Extensions.Configuration.Binder.SourceGeneration.* |
 |  __`SYSLIB1118`__ | *_`SYSLIB1100`-`SYSLIB1118` reserved for Microsoft.Extensions.Configuration.Binder.SourceGeneration.* |
-
+|  __`SYSLIB1201`__ | Options validation generator: Can't use 'ValidateObjectMembersAttribute' or `ValidateEnumeratedItemsAttribute` on fields or properties with open generic types. |
+|  __`SYSLIB1202`__ | Options validation generator: A member type has no fields or properties to validate. |
+|  __`SYSLIB1203`__ | Options validation generator: A type has no fields or properties to validate. |
+|  __`SYSLIB1204`__ | Options validation generator: A type annotated with `OptionsValidatorAttribute` doesn't implement the necessary interface. |
+|  __`SYSLIB1205`__ | Options validation generator: A type already includes an implementation of the 'Validate' method. |
+|  __`SYSLIB1206`__ | Options validation generator: Can't validate private fields or properties. |
+|  __`SYSLIB1207`__ | Options validation generator: Member type is not enumerable. |
+|  __`SYSLIB1208`__ | Options validation generator: Validators used for transitive or enumerable validation must have a constructor with no parameters. |
+|  __`SYSLIB1209`__ | Options validation generator: `OptionsValidatorAttribute` can't be applied to a static class. |
+|  __`SYSLIB1210`__ | Options validation generator: Null validator type specified for the `ValidateObjectMembersAttribute` or 'ValidateEnumeratedItemsAttribute' attributes. |
+|  __`SYSLIB1211`__ | Options validation generator: Unsupported circular references in model types. |
+|  __`SYSLIB1212`__ | Options validation generator: Member potentially missing transitive validation. |
+|  __`SYSLIB1213`__ | Options validation generator: Member potentially missing enumerable validation. |
+|  __`SYSLIB1214`__ | *_`SYSLIB1214`-`SYSLIB1218` reserved for Microsoft.Extensions.Options.SourceGeneration.* |
+|  __`SYSLIB1215`__ | *_`SYSLIB1214`-`SYSLIB1218` reserved for Microsoft.Extensions.Options.SourceGeneration.* |
+|  __`SYSLIB1216`__ | *_`SYSLIB1214`-`SYSLIB1218` reserved for Microsoft.Extensions.Options.SourceGeneration.* |
+|  __`SYSLIB1217`__ | *_`SYSLIB1214`-`SYSLIB1218` reserved for Microsoft.Extensions.Options.SourceGeneration.* |
+|  __`SYSLIB1218`__ | *_`SYSLIB1214`-`SYSLIB1218` reserved for Microsoft.Extensions.Options.SourceGeneration.* |
 
 ### Diagnostic Suppressions (`SYSLIBSUPPRESS****`)
 
index 921394b..4257c05 100644 (file)
@@ -31,6 +31,46 @@ namespace System
         [DoesNotReturn]
 #endif
         private static void Throw(string? paramName) => throw new ArgumentNullException(paramName);
+
+        /// <summary>
+        /// Throws either an <see cref="System.ArgumentNullException"/> or an <see cref="System.ArgumentException"/>
+        /// if the specified string is <see langword="null"/> or whitespace respectively.
+        /// </summary>
+        /// <param name="argument">String to be checked for <see langword="null"/> or whitespace.</param>
+        /// <param name="paramName">The name of the parameter being checked.</param>
+        /// <returns>The original value of <paramref name="argument"/>.</returns>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#if NETCOREAPP3_0_OR_GREATER
+        [return: NotNull]
+#endif
+        public static string IfNullOrWhitespace(
+#if NETCOREAPP3_0_OR_GREATER
+            [NotNull]
+#endif
+            string? argument,
+            [CallerArgumentExpression(nameof(argument))] string paramName = "")
+        {
+#if !NETCOREAPP3_1_OR_GREATER
+            if (argument == null)
+            {
+                throw new ArgumentNullException(paramName);
+            }
+#endif
+
+            if (string.IsNullOrWhiteSpace(argument))
+            {
+                if (argument == null)
+                {
+                    throw new ArgumentNullException(paramName);
+                }
+                else
+                {
+                    throw new ArgumentException(paramName, "Argument is whitespace");
+                }
+            }
+
+            return argument;
+        }
     }
 }
 
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/DiagDescriptors.cs b/src/libraries/Microsoft.Extensions.Options/gen/DiagDescriptors.cs
new file mode 100644 (file)
index 0000000..e76363f
--- /dev/null
@@ -0,0 +1,95 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.CodeAnalysis;
+using System;
+
+namespace Microsoft.Extensions.Options.Generators
+{
+    internal sealed class DiagDescriptors : DiagDescriptorsBase
+    {
+        private const string Category = "Microsoft.Extensions.Options.SourceGeneration";
+
+        public static DiagnosticDescriptor CantUseWithGenericTypes { get; } = Make(
+            id: "SYSLIB1201",
+            title: SR.CantUseWithGenericTypesTitle,
+            messageFormat: SR.CantUseWithGenericTypesMessage,
+            category: Category);
+
+        public static DiagnosticDescriptor NoEligibleMember { get; } = Make(
+            id: "SYSLIB1202",
+            title: SR.NoEligibleMemberTitle,
+            messageFormat: SR.NoEligibleMemberMessage,
+            category: Category,
+            defaultSeverity: DiagnosticSeverity.Warning);
+
+        public static DiagnosticDescriptor NoEligibleMembersFromValidator { get; } = Make(
+            id: "SYSLIB1203",
+            title: SR.NoEligibleMembersFromValidatorTitle,
+            messageFormat: SR.NoEligibleMembersFromValidatorMessage,
+            category: Category,
+            defaultSeverity: DiagnosticSeverity.Warning);
+
+        public static DiagnosticDescriptor DoesntImplementIValidateOptions { get; } = Make(
+            id: "SYSLIB1204",
+            title: SR.DoesntImplementIValidateOptionsTitle,
+            messageFormat: SR.DoesntImplementIValidateOptionsMessage,
+            category: Category);
+
+        public static DiagnosticDescriptor AlreadyImplementsValidateMethod { get; } = Make(
+            id: "SYSLIB1205",
+            title: SR.AlreadyImplementsValidateMethodTitle,
+            messageFormat: SR.AlreadyImplementsValidateMethodMessage,
+            category: Category);
+
+        public static DiagnosticDescriptor MemberIsInaccessible { get; } = Make(
+            id: "SYSLIB1206",
+            title: SR.MemberIsInaccessibleTitle,
+            messageFormat: SR.MemberIsInaccessibleMessage,
+            category: Category);
+
+        public static DiagnosticDescriptor NotEnumerableType { get; } = Make(
+            id: "SYSLIB1207",
+            title: SR.NotEnumerableTypeTitle,
+            messageFormat: SR.NotEnumerableTypeMessage,
+            category: Category);
+
+        public static DiagnosticDescriptor ValidatorsNeedSimpleConstructor { get; } = Make(
+            id: "SYSLIB1208",
+            title: SR.ValidatorsNeedSimpleConstructorTitle,
+            messageFormat: SR.ValidatorsNeedSimpleConstructorMessage,
+            category: Category);
+
+        public static DiagnosticDescriptor CantBeStaticClass { get; } = Make(
+            id: "SYSLIB1209",
+            title: SR.CantBeStaticClassTitle,
+            messageFormat: SR.CantBeStaticClassMessage,
+            category: Category);
+
+        public static DiagnosticDescriptor NullValidatorType { get; } = Make(
+            id: "SYSLIB1210",
+            title: SR.NullValidatorTypeTitle,
+            messageFormat: SR.NullValidatorTypeMessage,
+            category: Category);
+
+        public static DiagnosticDescriptor CircularTypeReferences { get; } = Make(
+            id: "SYSLIB1211",
+            title: SR.CircularTypeReferencesTitle,
+            messageFormat: SR.CircularTypeReferencesMessage,
+            category: Category);
+
+        public static DiagnosticDescriptor PotentiallyMissingTransitiveValidation { get; } = Make(
+            id: "SYSLIB1212",
+            title: SR.PotentiallyMissingTransitiveValidationTitle,
+            messageFormat: SR.PotentiallyMissingTransitiveValidationMessage,
+            category: Category,
+            defaultSeverity: DiagnosticSeverity.Warning);
+
+        public static DiagnosticDescriptor PotentiallyMissingEnumerableValidation { get; } = Make(
+            id: "SYSLIB1213",
+            title: SR.PotentiallyMissingEnumerableValidationTitle,
+            messageFormat: SR.PotentiallyMissingEnumerableValidationMessage,
+            category: Category,
+            defaultSeverity: DiagnosticSeverity.Warning);
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/DiagDescriptorsBase.cs b/src/libraries/Microsoft.Extensions.Options/gen/DiagDescriptorsBase.cs
new file mode 100644 (file)
index 0000000..f749e02
--- /dev/null
@@ -0,0 +1,30 @@
+// 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 Microsoft.CodeAnalysis;
+
+namespace Microsoft.Extensions.Options.Generators
+{
+    #pragma warning disable CA1052 // Static holder types should be Static or NotInheritable
+    internal class DiagDescriptorsBase
+    #pragma warning restore CA1052
+    {
+        protected static DiagnosticDescriptor Make(
+                string id,
+                string title,
+                string messageFormat,
+                string category,
+                DiagnosticSeverity defaultSeverity = DiagnosticSeverity.Error,
+                bool isEnabledByDefault = true)
+        {
+            return new(
+                id,
+                title,
+                messageFormat,
+                category,
+                defaultSeverity,
+                isEnabledByDefault);
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Emitter.cs b/src/libraries/Microsoft.Extensions.Options/gen/Emitter.cs
new file mode 100644 (file)
index 0000000..91ad41a
--- /dev/null
@@ -0,0 +1,387 @@
+// 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.Linq;
+using System.Threading;
+
+namespace Microsoft.Extensions.Options.Generators
+{
+    /// <summary>
+    /// Emits option validation.
+    /// </summary>
+    internal sealed class Emitter : EmitterBase
+    {
+        private const string StaticValidationAttributeHolderClassName = "__Attributes";
+        private const string StaticValidatorHolderClassName = "__Validators";
+        private const string StaticFieldHolderClassesNamespace = "__OptionValidationStaticInstances";
+        private const string StaticValidationAttributeHolderClassFQN = $"global::{StaticFieldHolderClassesNamespace}.{StaticValidationAttributeHolderClassName}";
+        private const string StaticValidatorHolderClassFQN = $"global::{StaticFieldHolderClassesNamespace}.{StaticValidatorHolderClassName}";
+        private const string StaticListType = "global::System.Collections.Generic.List";
+        private const string StaticValidationResultType = "global::System.ComponentModel.DataAnnotations.ValidationResult";
+        private const string StaticValidationAttributeType = "global::System.ComponentModel.DataAnnotations.ValidationAttribute";
+
+        private sealed record StaticFieldInfo(string FieldTypeFQN, int FieldOrder, string FieldName, IList<string> InstantiationLines);
+
+        public string Emit(
+            IEnumerable<ValidatorType> validatorTypes,
+            CancellationToken cancellationToken)
+        {
+            var staticValidationAttributesDict = new Dictionary<string, StaticFieldInfo>();
+            var staticValidatorsDict = new Dictionary<string, StaticFieldInfo>();
+
+            foreach (var vt in validatorTypes.OrderBy(static lt => lt.Namespace + "." + lt.Name))
+            {
+                cancellationToken.ThrowIfCancellationRequested();
+                GenValidatorType(vt, ref staticValidationAttributesDict, ref staticValidatorsDict);
+            }
+
+            GenStaticClassWithStaticReadonlyFields(staticValidationAttributesDict.Values, StaticFieldHolderClassesNamespace, StaticValidationAttributeHolderClassName);
+            GenStaticClassWithStaticReadonlyFields(staticValidatorsDict.Values, StaticFieldHolderClassesNamespace, StaticValidatorHolderClassName);
+
+            return Capture();
+        }
+
+        private void GenValidatorType(ValidatorType vt, ref Dictionary<string, StaticFieldInfo> staticValidationAttributesDict, ref Dictionary<string, StaticFieldInfo> staticValidatorsDict)
+        {
+            if (vt.Namespace.Length > 0)
+            {
+                OutLn($"namespace {vt.Namespace}");
+                OutOpenBrace();
+            }
+
+            foreach (var p in vt.ParentTypes)
+            {
+                OutLn(p);
+                OutOpenBrace();
+            }
+
+            if (vt.IsSynthetic)
+            {
+                OutGeneratedCodeAttribute();
+                OutLn($"internal sealed partial {vt.DeclarationKeyword} {vt.Name}");
+            }
+            else
+            {
+                OutLn($"partial {vt.DeclarationKeyword} {vt.Name}");
+            }
+
+            OutOpenBrace();
+
+            for (var i = 0; i < vt.ModelsToValidate.Count; i++)
+            {
+                var modelToValidate = vt.ModelsToValidate[i];
+
+                GenModelValidationMethod(modelToValidate, vt.IsSynthetic, ref staticValidationAttributesDict, ref staticValidatorsDict);
+            }
+
+            OutCloseBrace();
+
+            foreach (var _ in vt.ParentTypes)
+            {
+                OutCloseBrace();
+            }
+
+            if (vt.Namespace.Length > 0)
+            {
+                OutCloseBrace();
+            }
+        }
+
+        private void GenStaticClassWithStaticReadonlyFields(IEnumerable<StaticFieldInfo> staticFields, string classNamespace, string className)
+        {
+            OutLn($"namespace {classNamespace}");
+            OutOpenBrace();
+
+            OutGeneratedCodeAttribute();
+            OutLn($"internal static class {className}");
+            OutOpenBrace();
+
+            var staticValidationAttributes = staticFields
+                .OrderBy(x => x.FieldOrder)
+                .ToArray();
+
+            for (var i = 0; i < staticValidationAttributes.Length; i++)
+            {
+                var attributeInstance = staticValidationAttributes[i];
+                OutIndent();
+                Out($"internal static readonly {attributeInstance.FieldTypeFQN} {attributeInstance.FieldName} = ");
+                for (var j = 0; j < attributeInstance.InstantiationLines.Count; j++)
+                {
+                    var line = attributeInstance.InstantiationLines[j];
+                    Out(line);
+                    if (j != attributeInstance.InstantiationLines.Count - 1)
+                    {
+                        OutLn();
+                        OutIndent();
+                    }
+                    else
+                    {
+                        Out(';');
+                    }
+                }
+
+                OutLn();
+
+                if (i != staticValidationAttributes.Length - 1)
+                {
+                    OutLn();
+                }
+            }
+
+            OutCloseBrace();
+
+            OutCloseBrace();
+        }
+
+        private void GenModelSelfValidationIfNecessary(ValidatedModel modelToValidate)
+        {
+            if (modelToValidate.SelfValidates)
+            {
+                OutLn($"builder.AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context));");
+                OutLn();
+            }
+        }
+
+        private void GenModelValidationMethod(
+            ValidatedModel modelToValidate,
+            bool makeStatic,
+            ref Dictionary<string, StaticFieldInfo> staticValidationAttributesDict,
+            ref Dictionary<string, StaticFieldInfo> staticValidatorsDict)
+        {
+            OutLn($"/// <summary>");
+            OutLn($"/// Validates a specific named options instance (or all when <paramref name=\"name\"/> is <see langword=\"null\" />).");
+            OutLn($"/// </summary>");
+            OutLn($"/// <param name=\"name\">The name of the options instance being validated.</param>");
+            OutLn($"/// <param name=\"options\">The options instance.</param>");
+            OutLn($"/// <returns>Validation result.</returns>");
+            OutGeneratedCodeAttribute();
+
+            OutLn($"public {(makeStatic ? "static " : string.Empty)}global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, {modelToValidate.Name} options)");
+            OutOpenBrace();
+            OutLn($"var baseName = (string.IsNullOrEmpty(name) ? \"{modelToValidate.SimpleName}\" : name) + \".\";");
+            OutLn($"var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();");
+            OutLn($"var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);");
+
+            int capacity = modelToValidate.MembersToValidate.Max(static vm => vm.ValidationAttributes.Count);
+            if (capacity > 0)
+            {
+                OutLn($"var validationResults = new {StaticListType}<{StaticValidationResultType}>();");
+                OutLn($"var validationAttributes = new {StaticListType}<{StaticValidationAttributeType}>({capacity});");
+            }
+            OutLn();
+
+            bool cleanListsBeforeUse = false;
+            foreach (var vm in modelToValidate.MembersToValidate)
+            {
+                if (vm.ValidationAttributes.Count > 0)
+                {
+                    GenMemberValidation(vm, ref staticValidationAttributesDict, cleanListsBeforeUse);
+                    cleanListsBeforeUse = true;
+                    OutLn();
+                }
+
+                if (vm.TransValidatorType is not null)
+                {
+                    GenTransitiveValidation(vm, ref staticValidatorsDict);
+                    OutLn();
+                }
+
+                if (vm.EnumerationValidatorType is not null)
+                {
+                    GenEnumerationValidation(vm, ref staticValidatorsDict);
+                    OutLn();
+                }
+            }
+
+            GenModelSelfValidationIfNecessary(modelToValidate);
+            OutLn($"return builder.Build();");
+            OutCloseBrace();
+        }
+
+        private void GenMemberValidation(ValidatedMember vm, ref Dictionary<string, StaticFieldInfo> staticValidationAttributesDict, bool cleanListsBeforeUse)
+        {
+            OutLn($"context.MemberName = \"{vm.Name}\";");
+            OutLn($"context.DisplayName = baseName + \"{vm.Name}\";");
+
+            if (cleanListsBeforeUse)
+            {
+                OutLn($"validationResults.Clear();");
+                OutLn($"validationAttributes.Clear();");
+            }
+
+            foreach (var attr in vm.ValidationAttributes)
+            {
+                var staticValidationAttributeInstance = GetOrAddStaticValidationAttribute(ref staticValidationAttributesDict, attr);
+                OutLn($"validationAttributes.Add({StaticValidationAttributeHolderClassFQN}.{staticValidationAttributeInstance.FieldName});");
+            }
+
+            OutLn($"if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.{vm.Name}!, context, validationResults, validationAttributes))");
+            OutOpenBrace();
+            OutLn($"builder.AddResults(validationResults);");
+            OutCloseBrace();
+        }
+
+        private StaticFieldInfo GetOrAddStaticValidationAttribute(ref Dictionary<string, StaticFieldInfo> staticValidationAttributesDict, ValidationAttributeInfo attr)
+        {
+            var attrInstantiationStatementLines = new List<string>();
+
+            if (attr.ConstructorArguments.Count > 0)
+            {
+                attrInstantiationStatementLines.Add($"new {attr.AttributeName}(");
+
+                for (var i = 0; i < attr.ConstructorArguments.Count; i++)
+                {
+                    if (i != attr.ConstructorArguments.Count - 1)
+                    {
+                        attrInstantiationStatementLines.Add($"{GetPaddingString(1)}{attr.ConstructorArguments[i]},");
+                    }
+                    else
+                    {
+                        attrInstantiationStatementLines.Add($"{GetPaddingString(1)}{attr.ConstructorArguments[i]})");
+                    }
+                }
+            }
+            else
+            {
+                attrInstantiationStatementLines.Add($"new {attr.AttributeName}()");
+            }
+
+            if (attr.Properties.Count > 0)
+            {
+                attrInstantiationStatementLines.Add("{");
+
+                var propertiesOrderedByKey = attr.Properties
+                    .OrderBy(p => p.Key)
+                    .ToArray();
+
+                for (var i = 0; i < propertiesOrderedByKey.Length; i++)
+                {
+                    var prop = propertiesOrderedByKey[i];
+                    var notLast = i != propertiesOrderedByKey.Length - 1;
+                    attrInstantiationStatementLines.Add($"{GetPaddingString(1)}{prop.Key} = {prop.Value}{(notLast ? "," : string.Empty)}");
+                }
+
+                attrInstantiationStatementLines.Add("}");
+            }
+
+            var instantiationStatement = string.Join(Environment.NewLine, attrInstantiationStatementLines);
+
+            if (!staticValidationAttributesDict.TryGetValue(instantiationStatement, out var staticValidationAttributeInstance))
+            {
+                var fieldNumber = staticValidationAttributesDict.Count + 1;
+                staticValidationAttributeInstance = new StaticFieldInfo(
+                    FieldTypeFQN: attr.AttributeName,
+                    FieldOrder: fieldNumber,
+                    FieldName: $"A{fieldNumber}",
+                    InstantiationLines: attrInstantiationStatementLines);
+
+                staticValidationAttributesDict.Add(instantiationStatement, staticValidationAttributeInstance);
+            }
+
+            return staticValidationAttributeInstance;
+        }
+
+        private void GenTransitiveValidation(ValidatedMember vm, ref Dictionary<string, StaticFieldInfo> staticValidatorsDict)
+        {
+            string callSequence;
+            if (vm.TransValidateTypeIsSynthetic)
+            {
+                callSequence = vm.TransValidatorType!;
+            }
+            else
+            {
+                var staticValidatorInstance = GetOrAddStaticValidator(ref staticValidatorsDict, vm.TransValidatorType!);
+
+                callSequence = $"{StaticValidatorHolderClassFQN}.{staticValidatorInstance.FieldName}";
+            }
+
+            var valueAccess = (vm.IsNullable && vm.IsValueType) ? ".Value" : string.Empty;
+
+            if (vm.IsNullable)
+            {
+                OutLn($"if (options.{vm.Name} is not null)");
+                OutOpenBrace();
+                OutLn($"builder.AddResult({callSequence}.Validate(baseName + \"{vm.Name}\", options.{vm.Name}{valueAccess}));");
+                OutCloseBrace();
+            }
+            else
+            {
+                OutLn($"builder.AddResult({callSequence}.Validate(baseName + \"{vm.Name}\", options.{vm.Name}{valueAccess}));");
+            }
+        }
+
+        private void GenEnumerationValidation(ValidatedMember vm, ref Dictionary<string, StaticFieldInfo> staticValidatorsDict)
+        {
+            var valueAccess = (vm.IsValueType && vm.IsNullable) ? ".Value" : string.Empty;
+            var enumeratedValueAccess = (vm.EnumeratedIsNullable && vm.EnumeratedIsValueType) ? ".Value" : string.Empty;
+            string callSequence;
+            if (vm.EnumerationValidatorTypeIsSynthetic)
+            {
+                callSequence = vm.EnumerationValidatorType!;
+            }
+            else
+            {
+                var staticValidatorInstance = GetOrAddStaticValidator(ref staticValidatorsDict, vm.EnumerationValidatorType!);
+
+                callSequence = $"{StaticValidatorHolderClassFQN}.{staticValidatorInstance.FieldName}";
+            }
+
+            if (vm.IsNullable)
+            {
+                OutLn($"if (options.{vm.Name} is not null)");
+            }
+
+            OutOpenBrace();
+
+            OutLn($"var count = 0;");
+            OutLn($"foreach (var o in options.{vm.Name}{valueAccess})");
+            OutOpenBrace();
+
+            if (vm.EnumeratedIsNullable)
+            {
+                OutLn($"if (o is not null)");
+                OutOpenBrace();
+                OutLn($"builder.AddResult({callSequence}.Validate(baseName + $\"{vm.Name}[{{count}}]\", o{enumeratedValueAccess}));");
+                OutCloseBrace();
+
+                if (!vm.EnumeratedMayBeNull)
+                {
+                    OutLn($"else");
+                    OutOpenBrace();
+                    OutLn($"builder.AddError(baseName + $\"{vm.Name}[{{count}}] is null\");");
+                    OutCloseBrace();
+                }
+
+                OutLn($"count++;");
+            }
+            else
+            {
+                OutLn($"builder.AddResult({callSequence}.Validate(baseName + $\"{vm.Name}[{{count++}}]\", o{enumeratedValueAccess}));");
+            }
+
+            OutCloseBrace();
+            OutCloseBrace();
+        }
+
+    #pragma warning disable CA1822 // Mark members as static: static should come before non-static, but we want the method to be here
+        private StaticFieldInfo GetOrAddStaticValidator(ref Dictionary<string, StaticFieldInfo> staticValidatorsDict, string validatorTypeFQN)
+    #pragma warning restore CA1822
+        {
+            if (!staticValidatorsDict.TryGetValue(validatorTypeFQN, out var staticValidatorInstance))
+            {
+                var fieldNumber = staticValidatorsDict.Count + 1;
+                staticValidatorInstance = new StaticFieldInfo(
+                    FieldTypeFQN: validatorTypeFQN,
+                    FieldOrder: fieldNumber,
+                    FieldName: $"V{fieldNumber}",
+                    InstantiationLines: new[] { $"new {validatorTypeFQN}()" });
+
+                staticValidatorsDict.Add(validatorTypeFQN, staticValidatorInstance);
+            }
+
+            return staticValidatorInstance;
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/EmitterBase.cs b/src/libraries/Microsoft.Extensions.Options/gen/EmitterBase.cs
new file mode 100644 (file)
index 0000000..890c9bc
--- /dev/null
@@ -0,0 +1,111 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.Extensions.Options.Generators
+{
+    internal class EmitterBase
+    {
+        public static string GeneratedCodeAttribute { get; } = $"global::System.CodeDom.Compiler.GeneratedCodeAttribute(" +
+                        $"\"{typeof(EmitterBase).Assembly.GetName().Name}\", " +
+                        $"\"{typeof(EmitterBase).Assembly.GetName().Version}\")";
+
+        public static string FilePreamble { get; } = @$"
+    // <auto-generated/>
+    #nullable enable
+    #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
+    ";
+
+        private const int DefaultStringBuilderCapacity = 1024;
+        private const int IndentChars = 4;
+
+        private readonly StringBuilder _sb = new(DefaultStringBuilderCapacity);
+        private readonly string[] _padding = new string[16];
+        private int _indent;
+
+        public EmitterBase(bool emitPreamble = true)
+        {
+            var padding = _padding;
+            for (int i = 0; i < padding.Length; i++)
+            {
+                padding[i] = new string(' ', i * IndentChars);
+            }
+
+            if (emitPreamble)
+            {
+                Out(FilePreamble);
+            }
+        }
+
+        protected void OutOpenBrace()
+        {
+            OutLn("{");
+            Indent();
+        }
+
+        protected void OutCloseBrace()
+        {
+            Unindent();
+            OutLn("}");
+        }
+
+        protected void OutCloseBraceWithExtra(string extra)
+        {
+            Unindent();
+            OutIndent();
+            Out("}");
+            Out(extra);
+            OutLn();
+        }
+
+        protected void OutIndent()
+        {
+            _ = _sb.Append(_padding[_indent]);
+        }
+
+        protected string GetPaddingString(byte indent)
+        {
+            return _padding[indent];
+        }
+
+        protected void OutLn()
+        {
+            _ = _sb.AppendLine();
+        }
+
+        protected void OutLn(string line)
+        {
+            OutIndent();
+            _ = _sb.AppendLine(line);
+        }
+
+        protected void OutPP(string line)
+        {
+            _ = _sb.AppendLine(line);
+        }
+
+        protected void OutEnumeration(IEnumerable<string> e)
+        {
+            bool first = true;
+            foreach (var item in e)
+            {
+                if (!first)
+                {
+                    Out(", ");
+                }
+
+                Out(item);
+                first = false;
+            }
+        }
+
+        protected void Out(string text) => _ = _sb.Append(text);
+        protected void Out(char ch) => _ = _sb.Append(ch);
+        protected void Indent() => _indent++;
+        protected void Unindent() => _indent--;
+        protected void OutGeneratedCodeAttribute() => OutLn($"[{GeneratedCodeAttribute}]");
+        protected string Capture() => _sb.ToString();
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Generator.cs b/src/libraries/Microsoft.Extensions.Options/gen/Generator.cs
new file mode 100644 (file)
index 0000000..b24f43b
--- /dev/null
@@ -0,0 +1,53 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.Extensions.Options.Generators
+{
+    [Generator]
+    public class Generator : IIncrementalGenerator
+    {
+        public void Initialize(IncrementalGeneratorInitializationContext context)
+        {
+            IncrementalValuesProvider<(TypeDeclarationSyntax TypeSyntax, SemanticModel SemanticModel)> typeDeclarations = context.SyntaxProvider
+                .ForAttributeWithMetadataName(
+                    SymbolLoader.OptionsValidatorAttribute,
+                    (node, _) => node is TypeDeclarationSyntax,
+                    (context, _) => (TypeSyntax:context.TargetNode as TypeDeclarationSyntax, SemanticModel: context.SemanticModel))
+                .Where(static m => m.TypeSyntax is not null);
+
+            IncrementalValueProvider<(Compilation, ImmutableArray<(TypeDeclarationSyntax TypeSyntax, SemanticModel SemanticModel)>)> compilationAndTypes =
+                context.CompilationProvider.Combine(typeDeclarations.Collect());
+
+            context.RegisterSourceOutput(compilationAndTypes, static (spc, source) => HandleAnnotatedTypes(source.Item1, source.Item2, spc));
+        }
+
+        private static void HandleAnnotatedTypes(Compilation compilation, ImmutableArray<(TypeDeclarationSyntax TypeSyntax, SemanticModel SemanticModel)> types, SourceProductionContext context)
+        {
+            if (!SymbolLoader.TryLoad(compilation, out var symbolHolder))
+            {
+                // Not eligible compilation
+                return;
+            }
+
+            var parser = new Parser(compilation, context.ReportDiagnostic, symbolHolder!, context.CancellationToken);
+
+            var validatorTypes = parser.GetValidatorTypes(types);
+            if (validatorTypes.Count > 0)
+            {
+                var emitter = new Emitter();
+                var result = emitter.Emit(validatorTypes, context.CancellationToken);
+
+                context.AddSource("Validators.g.cs", SourceText.From(result, Encoding.UTF8));
+            }
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Microsoft.Extensions.Options.SourceGeneration.csproj b/src/libraries/Microsoft.Extensions.Options/gen/Microsoft.Extensions.Options.SourceGeneration.csproj
new file mode 100644 (file)
index 0000000..12bc4a7
--- /dev/null
@@ -0,0 +1,35 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <CLSCompliant>false</CLSCompliant>
+    <EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
+    <UsingToolXliff>true</UsingToolXliff>
+    <AnalyzerLanguage>cs</AnalyzerLanguage>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <AnalyzerRoslynVersion>4.4</AnalyzerRoslynVersion>
+    <RoslynApiVersion>$(MicrosoftCodeAnalysisVersion_4_4)</RoslynApiVersion>
+    <DefineConstants Condition="'$(LaunchDebugger)' == 'true'">$(DefineConstants);ROSLYN4_4_OR_GREATER</DefineConstants>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(MicrosoftCodeAnalysisVersion_4_4)" PrivateAssets="all" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Compile Include="$(CoreLibSharedDir)System\Runtime\CompilerServices\IsExternalInit.cs" Link="Common\System\Runtime\CompilerServices\IsExternalInit.cs" />
+    <Compile Include="DiagDescriptors.cs" />
+    <Compile Include="DiagDescriptorsBase.cs" />
+    <Compile Include="Emitter.cs" />
+    <Compile Include="EmitterBase.cs" />
+    <Compile Include="Generator.cs" />
+    <Compile Include="Model\ValidatedMember.cs" />
+    <Compile Include="Model\ValidatedModel.cs" />
+    <Compile Include="Model\ValidationAttributeInfo.cs" />
+    <Compile Include="Model\ValidatorType.cs" />
+    <Compile Include="Parser.cs" />
+    <Compile Include="ParserUtilities.cs" />
+    <Compile Include="SymbolHolder.cs" />
+    <Compile Include="SymbolLoader.cs" />
+    <Compile Include="TypeDeclarationSyntaxReceiver.cs" />
+  </ItemGroup>
+</Project>
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidatedMember.cs b/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidatedMember.cs
new file mode 100644 (file)
index 0000000..cfed013
--- /dev/null
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+namespace Microsoft.Extensions.Options.Generators
+{
+    internal sealed record class ValidatedMember(
+        string Name,
+        List<ValidationAttributeInfo> ValidationAttributes,
+        string? TransValidatorType,
+        bool TransValidateTypeIsSynthetic,
+        string? EnumerationValidatorType,
+        bool EnumerationValidatorTypeIsSynthetic,
+        bool IsNullable,
+        bool IsValueType,
+        bool EnumeratedIsNullable,
+        bool EnumeratedIsValueType,
+        bool EnumeratedMayBeNull);
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidatedModel.cs b/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidatedModel.cs
new file mode 100644 (file)
index 0000000..0d40d93
--- /dev/null
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+namespace Microsoft.Extensions.Options.Generators
+{
+    internal sealed record class ValidatedModel(
+        string Name,
+        string SimpleName,
+        bool SelfValidates,
+        List<ValidatedMember> MembersToValidate);
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidationAttributeInfo.cs b/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidationAttributeInfo.cs
new file mode 100644 (file)
index 0000000..419ceca
--- /dev/null
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+namespace Microsoft.Extensions.Options.Generators
+{
+    internal sealed record class ValidationAttributeInfo(string AttributeName)
+    {
+        public List<string> ConstructorArguments { get; } = new();
+        public Dictionary<string, string> Properties { get; } = new();
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidatorType.cs b/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidatorType.cs
new file mode 100644 (file)
index 0000000..e104322
--- /dev/null
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+namespace Microsoft.Extensions.Options.Generators
+{
+    internal sealed record class ValidatorType(
+        string Namespace,
+        string Name,
+        string NameWithoutGenerics,
+        string DeclarationKeyword,
+        List<string> ParentTypes,
+        bool IsSynthetic,
+        IList<ValidatedModel> ModelsToValidate);
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Parser.cs b/src/libraries/Microsoft.Extensions.Options/gen/Parser.cs
new file mode 100644 (file)
index 0000000..2a9215c
--- /dev/null
@@ -0,0 +1,664 @@
+// 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.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Microsoft.Extensions.Options.Generators
+{
+    /// <summary>
+    /// Holds an internal parser class that extracts necessary information for generating IValidateOptions.
+    /// </summary>
+    internal sealed class Parser
+    {
+        private const int NumValidationMethodArgs = 2;
+
+        private readonly CancellationToken _cancellationToken;
+        private readonly Compilation _compilation;
+        private readonly Action<Diagnostic> _reportDiagnostic;
+        private readonly SymbolHolder _symbolHolder;
+        private readonly Dictionary<ITypeSymbol, ValidatorType> _synthesizedValidators = new(SymbolEqualityComparer.Default);
+        private readonly HashSet<ITypeSymbol> _visitedModelTypes = new(SymbolEqualityComparer.Default);
+
+        public Parser(
+            Compilation compilation,
+            Action<Diagnostic> reportDiagnostic,
+            SymbolHolder symbolHolder,
+            CancellationToken cancellationToken)
+        {
+            _compilation = compilation;
+            _cancellationToken = cancellationToken;
+            _reportDiagnostic = reportDiagnostic;
+            _symbolHolder = symbolHolder;
+        }
+
+        public IReadOnlyList<ValidatorType> GetValidatorTypes(IEnumerable<(TypeDeclarationSyntax TypeSyntax, SemanticModel SemanticModel)> classes)
+        {
+            var results = new List<ValidatorType>();
+
+            foreach (var group in classes.GroupBy(x => x.TypeSyntax.SyntaxTree))
+            {
+                SemanticModel? sm = null;
+                foreach (var typeDec in group)
+                {
+                    TypeDeclarationSyntax syntax = typeDec.TypeSyntax;
+                    _cancellationToken.ThrowIfCancellationRequested();
+                    sm ??= typeDec.SemanticModel;
+
+                    var validatorType = sm.GetDeclaredSymbol(syntax) as ITypeSymbol;
+                    if (validatorType is not null)
+                    {
+                        if (validatorType.IsStatic)
+                        {
+                            Diag(DiagDescriptors.CantBeStaticClass, syntax.GetLocation());
+                            continue;
+                        }
+
+                        _visitedModelTypes.Clear();
+
+                        var modelTypes = GetModelTypes(validatorType);
+                        if (modelTypes.Count == 0)
+                        {
+                            // validator doesn't implement IValidateOptions
+                            Diag(DiagDescriptors.DoesntImplementIValidateOptions, syntax.GetLocation(), validatorType.Name);
+                            continue;
+                        }
+
+                        var modelsValidatorTypeValidates = new List<ValidatedModel>(modelTypes.Count);
+
+                        foreach (var modelType in modelTypes)
+                        {
+                            if (modelType.Kind == SymbolKind.ErrorType)
+                            {
+                                // the compiler will report this error for us
+                                continue;
+                            }
+                            else
+                            {
+                                // keep track of the models we look at, to detect loops
+                                _ = _visitedModelTypes.Add(modelType.WithNullableAnnotation(NullableAnnotation.None));
+                            }
+
+                            if (AlreadyImplementsValidateMethod(validatorType, modelType))
+                            {
+                                // this type already implements a validation function, we can't auto-generate a new one
+                                Diag(DiagDescriptors.AlreadyImplementsValidateMethod, syntax.GetLocation(), validatorType.Name);
+                                continue;
+                            }
+
+                            var membersToValidate = GetMembersToValidate(modelType, true);
+                            if (membersToValidate.Count == 0)
+                            {
+                                // this type lacks any eligible members
+                                Diag(DiagDescriptors.NoEligibleMembersFromValidator, syntax.GetLocation(), modelType.ToString(), validatorType.ToString());
+                                continue;
+                            }
+
+                            modelsValidatorTypeValidates.Add(new ValidatedModel(
+                                GetFQN(modelType),
+                                modelType.Name,
+                                ModelSelfValidates(modelType),
+                                membersToValidate));
+                        }
+
+                        string keyword = GetTypeKeyword(validatorType);
+
+                        // following code establishes the containment hierarchy for the generated type in terms of nested types
+
+                        var parents = new List<string>();
+                        var parent = syntax.Parent as TypeDeclarationSyntax;
+
+                        while (parent is not null && IsAllowedKind(parent.Kind()))
+                        {
+                            parents.Add($"partial {GetTypeKeyword(parent)} {parent.Identifier}{parent.TypeParameterList} {parent.ConstraintClauses}");
+                            parent = parent.Parent as TypeDeclarationSyntax;
+                        }
+
+                        parents.Reverse();
+
+                        results.Add(new ValidatorType(
+                            validatorType.ContainingNamespace.IsGlobalNamespace ? string.Empty : validatorType.ContainingNamespace.ToString(),
+                            GetMinimalFQN(validatorType),
+                            GetMinimalFQNWithoutGenerics(validatorType),
+                            keyword,
+                            parents,
+                            false,
+                            modelsValidatorTypeValidates));
+                    }
+                }
+            }
+
+            results.AddRange(_synthesizedValidators.Values);
+            _synthesizedValidators.Clear();
+
+            return results;
+        }
+
+        private static bool IsAllowedKind(SyntaxKind kind) =>
+            kind == SyntaxKind.ClassDeclaration ||
+            kind == SyntaxKind.StructDeclaration ||
+            kind == SyntaxKind.RecordStructDeclaration ||
+            kind == SyntaxKind.RecordDeclaration;
+
+        private static string GetTypeKeyword(ITypeSymbol type)
+        {
+            if (type.IsReferenceType)
+            {
+                return type.IsRecord ? "record class" : "class";
+            }
+
+            return type.IsRecord ? "record struct" : "struct";
+        }
+
+        private static string GetTypeKeyword(TypeDeclarationSyntax type) =>
+            type.Kind() switch
+            {
+                SyntaxKind.ClassDeclaration => "class",
+                SyntaxKind.RecordDeclaration => "record class",
+                SyntaxKind.RecordStructDeclaration => "record struct",
+                _ => type.Keyword.ValueText,
+            };
+
+        private static string GetFQN(ISymbol type)
+            => type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier));
+
+        private static string GetMinimalFQN(ISymbol type)
+            => type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat.AddGenericsOptions(SymbolDisplayGenericsOptions.IncludeTypeParameters));
+
+        private static string GetMinimalFQNWithoutGenerics(ISymbol type)
+            => type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat.WithGenericsOptions(SymbolDisplayGenericsOptions.None));
+
+        /// <summary>
+        /// Checks whether the given validator already implement the IValidationOptions&gt;T&lt; interface.
+        /// </summary>
+        private static bool AlreadyImplementsValidateMethod(INamespaceOrTypeSymbol validatorType, ISymbol modelType)
+            => validatorType
+                .GetMembers("Validate")
+                .Where(m => m.Kind == SymbolKind.Method)
+                .Select(m => (IMethodSymbol)m)
+                .Any(m => m.Parameters.Length == NumValidationMethodArgs
+                    && m.Parameters[0].Type.SpecialType == SpecialType.System_String
+                    && SymbolEqualityComparer.Default.Equals(m.Parameters[1].Type, modelType));
+
+        /// <summary>
+        /// Checks whether the given type contain any unbound generic type arguments.
+        /// </summary>
+        private static bool HasOpenGenerics(ITypeSymbol type, out string genericType)
+        {
+            if (type is INamedTypeSymbol mt)
+            {
+                if (mt.IsGenericType)
+                {
+                    foreach (var ta in mt.TypeArguments)
+                    {
+                        if (ta.TypeKind == TypeKind.TypeParameter)
+                        {
+                            genericType = ta.Name;
+                            return true;
+                        }
+                    }
+                }
+            }
+            else if (type is ITypeParameterSymbol)
+            {
+                genericType = type.Name;
+                return true;
+            }
+            else if (type is IArrayTypeSymbol ats)
+            {
+                return HasOpenGenerics(ats.ElementType, out genericType);
+            }
+
+            genericType = string.Empty;
+            return false;
+        }
+
+        private ITypeSymbol? GetEnumeratedType(ITypeSymbol type)
+        {
+            if (type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
+            {
+                // extract the T from a Nullable<T>
+                type = ((INamedTypeSymbol)type).TypeArguments[0];
+            }
+
+            foreach (var implementingInterface in type.AllInterfaces)
+            {
+                if (SymbolEqualityComparer.Default.Equals(implementingInterface.OriginalDefinition, _compilation.GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T)))
+                {
+                    return implementingInterface.TypeArguments.First();
+                }
+            }
+
+            return null;
+        }
+
+        private List<ValidatedMember> GetMembersToValidate(ITypeSymbol modelType, bool speculate)
+        {
+            // make a list of the most derived members in the model type
+
+            if (modelType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
+            {
+                // extract the T from a Nullable<T>
+                modelType = ((INamedTypeSymbol)modelType).TypeArguments[0];
+            }
+
+            var members = modelType.GetMembers().ToList();
+            var addedMembers = new HashSet<string>(members.Select(m => m.Name));
+            var baseType = modelType.BaseType;
+            while (baseType is not null && baseType.SpecialType != SpecialType.System_Object)
+            {
+                var baseMembers = baseType.GetMembers().Where(m => !addedMembers.Contains(m.Name));
+                members.AddRange(baseMembers);
+                addedMembers.UnionWith(baseMembers.Select(m => m.Name));
+                baseType = baseType.BaseType;
+            }
+
+            var membersToValidate = new List<ValidatedMember>();
+            foreach (var member in members)
+            {
+                var memberInfo = GetMemberInfo(member, speculate);
+                if (memberInfo is not null)
+                {
+                    if (member.DeclaredAccessibility != Accessibility.Public && member.DeclaredAccessibility != Accessibility.Internal)
+                    {
+                        Diag(DiagDescriptors.MemberIsInaccessible, member.Locations.First(), member.Name);
+                        continue;
+                    }
+
+                    membersToValidate.Add(memberInfo);
+                }
+            }
+
+            return membersToValidate;
+        }
+
+        private ValidatedMember? GetMemberInfo(ISymbol member, bool speculate)
+        {
+            ITypeSymbol memberType;
+            switch (member)
+            {
+                case IPropertySymbol prop:
+                    memberType = prop.Type;
+                    break;
+                case IFieldSymbol field:
+                    if (field.AssociatedSymbol is not null)
+                    {
+                        // a backing field for a property, don't need those
+                        return null;
+                    }
+
+                    memberType = field.Type;
+                    break;
+                default:
+                    // we only care about properties and fields
+                    return null;
+            }
+
+            var validationAttrs = new List<ValidationAttributeInfo>();
+            string? transValidatorTypeName = null;
+            string? enumerationValidatorTypeName = null;
+            var enumeratedIsNullable = false;
+            var enumeratedIsValueType = false;
+            var enumeratedMayBeNull = false;
+            var transValidatorIsSynthetic = false;
+            var enumerationValidatorIsSynthetic = false;
+
+            foreach (var attribute in member.GetAttributes().Where(a => a.AttributeClass is not null))
+            {
+                var attributeType = attribute.AttributeClass!;
+                var attrLoc = attribute.ApplicationSyntaxReference?.GetSyntax().GetLocation();
+
+                if (SymbolEqualityComparer.Default.Equals(attributeType, _symbolHolder.ValidateObjectMembersAttributeSymbol))
+                {
+                    if (HasOpenGenerics(memberType, out var genericType))
+                    {
+                        Diag(DiagDescriptors.CantUseWithGenericTypes, attrLoc, genericType);
+    #pragma warning disable S1226 // Method parameters, caught exceptions and foreach variables' initial values should not be ignored
+                        speculate = false;
+    #pragma warning restore S1226 // Method parameters, caught exceptions and foreach variables' initial values should not be ignored
+                        continue;
+                    }
+
+                    if (attribute.ConstructorArguments.Length == 1)
+                    {
+                        var transValidatorType = attribute.ConstructorArguments[0].Value as INamedTypeSymbol;
+                        if (transValidatorType is not null)
+                        {
+                            if (CanValidate(transValidatorType, memberType))
+                            {
+                                if (transValidatorType.Constructors.Where(c => !c.Parameters.Any()).Any())
+                                {
+                                    transValidatorTypeName = transValidatorType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+                                }
+                                else
+                                {
+                                    Diag(DiagDescriptors.ValidatorsNeedSimpleConstructor, attrLoc, transValidatorType.Name);
+                                }
+                            }
+                            else
+                            {
+                                Diag(DiagDescriptors.DoesntImplementIValidateOptions, attrLoc, transValidatorType.Name, memberType.Name);
+                            }
+                        }
+                        else
+                        {
+                            Diag(DiagDescriptors.NullValidatorType, attrLoc);
+                        }
+                    }
+                    else if (!_visitedModelTypes.Add(memberType.WithNullableAnnotation(NullableAnnotation.None)))
+                    {
+                        Diag(DiagDescriptors.CircularTypeReferences, attrLoc, memberType.ToString());
+                        speculate = false;
+                        continue;
+                    }
+
+                    if (transValidatorTypeName == null)
+                    {
+                        transValidatorIsSynthetic = true;
+                        transValidatorTypeName = AddSynthesizedValidator(memberType, member);
+                    }
+
+                    // pop the stack
+                    _ = _visitedModelTypes.Remove(memberType.WithNullableAnnotation(NullableAnnotation.None));
+                }
+                else if (SymbolEqualityComparer.Default.Equals(attributeType, _symbolHolder.ValidateEnumeratedItemsAttributeSymbol))
+                {
+                    var enumeratedType = GetEnumeratedType(memberType);
+                    if (enumeratedType == null)
+                    {
+                        Diag(DiagDescriptors.NotEnumerableType, attrLoc, memberType);
+                        speculate = false;
+                        continue;
+                    }
+
+                    enumeratedIsNullable = enumeratedType.IsReferenceType || enumeratedType.NullableAnnotation == NullableAnnotation.Annotated;
+                    enumeratedIsValueType = enumeratedType.IsValueType;
+                    enumeratedMayBeNull = enumeratedType.NullableAnnotation == NullableAnnotation.Annotated;
+
+                    if (HasOpenGenerics(enumeratedType, out var genericType))
+                    {
+                        Diag(DiagDescriptors.CantUseWithGenericTypes, attrLoc, genericType);
+                        speculate = false;
+                        continue;
+                    }
+
+                    if (attribute.ConstructorArguments.Length == 1)
+                    {
+                        var enumerationValidatorType = attribute.ConstructorArguments[0].Value as INamedTypeSymbol;
+                        if (enumerationValidatorType is not null)
+                        {
+                            if (CanValidate(enumerationValidatorType, enumeratedType))
+                            {
+                                if (enumerationValidatorType.Constructors.Where(c => c.Parameters.Length == 0).Any())
+                                {
+                                    enumerationValidatorTypeName = enumerationValidatorType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+                                }
+                                else
+                                {
+                                    Diag(DiagDescriptors.ValidatorsNeedSimpleConstructor, attrLoc, enumerationValidatorType.Name);
+                                }
+                            }
+                            else
+                            {
+                                Diag(DiagDescriptors.DoesntImplementIValidateOptions, attrLoc, enumerationValidatorType.Name, enumeratedType.Name);
+                            }
+                        }
+                        else
+                        {
+                            Diag(DiagDescriptors.NullValidatorType, attrLoc);
+                        }
+                    }
+                    else if (!_visitedModelTypes.Add(enumeratedType.WithNullableAnnotation(NullableAnnotation.None)))
+                    {
+                        Diag(DiagDescriptors.CircularTypeReferences, attrLoc, enumeratedType.ToString());
+                        speculate = false;
+                        continue;
+                    }
+
+                    if (enumerationValidatorTypeName == null)
+                    {
+                        enumerationValidatorIsSynthetic = true;
+                        enumerationValidatorTypeName = AddSynthesizedValidator(enumeratedType, member);
+                    }
+
+                    // pop the stack
+                    _ = _visitedModelTypes.Remove(enumeratedType.WithNullableAnnotation(NullableAnnotation.None));
+                }
+                else if (ConvertTo(attributeType, _symbolHolder.ValidationAttributeSymbol))
+                {
+                    var validationAttr = new ValidationAttributeInfo(attributeType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
+                    validationAttrs.Add(validationAttr);
+
+                    foreach (var constructorArgument in attribute.ConstructorArguments)
+                    {
+                        validationAttr.ConstructorArguments.Add(GetArgumentExpression(constructorArgument.Type!, constructorArgument.Value));
+                    }
+
+                    foreach (var namedArgument in attribute.NamedArguments)
+                    {
+                        validationAttr.Properties.Add(namedArgument.Key, GetArgumentExpression(namedArgument.Value.Type!, namedArgument.Value.Value));
+                    }
+                }
+            }
+
+            // generate a warning if the field/property seems like it should be transitively validated
+            if (transValidatorTypeName == null && speculate && memberType.SpecialType == SpecialType.None)
+            {
+                if (!HasOpenGenerics(memberType, out var genericType))
+                {
+                    var membersToValidate = GetMembersToValidate(memberType, false);
+                    if (membersToValidate.Count > 0)
+                    {
+                        Diag(DiagDescriptors.PotentiallyMissingTransitiveValidation, member.GetLocation(), memberType.Name, member.Name);
+                    }
+                }
+            }
+
+            // generate a warning if the field/property seems like it should be enumerated
+            if (enumerationValidatorTypeName == null && speculate)
+            {
+                var enumeratedType = GetEnumeratedType(memberType);
+                if (enumeratedType is not null)
+                {
+                    if (!HasOpenGenerics(enumeratedType, out var genericType))
+                    {
+                        var membersToValidate = GetMembersToValidate(enumeratedType, false);
+                        if (membersToValidate.Count > 0)
+                        {
+                            Diag(DiagDescriptors.PotentiallyMissingEnumerableValidation, member.GetLocation(), enumeratedType.Name, member.Name);
+                        }
+                    }
+                }
+            }
+
+            if (validationAttrs.Count > 0 || transValidatorTypeName is not null || enumerationValidatorTypeName is not null)
+            {
+                return new(
+                    member.Name,
+                    validationAttrs,
+                    transValidatorTypeName,
+                    transValidatorIsSynthetic,
+                    enumerationValidatorTypeName,
+                    enumerationValidatorIsSynthetic,
+                    memberType.IsReferenceType || memberType.NullableAnnotation == NullableAnnotation.Annotated,
+                    memberType.IsValueType,
+                    enumeratedIsNullable,
+                    enumeratedIsValueType,
+                    enumeratedMayBeNull);
+            }
+
+            return null;
+        }
+
+        private string? AddSynthesizedValidator(ITypeSymbol modelType, ISymbol member)
+        {
+            var mt = modelType.WithNullableAnnotation(NullableAnnotation.None);
+            if (mt.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
+            {
+                // extract the T from a Nullable<T>
+                mt = ((INamedTypeSymbol)mt).TypeArguments[0];
+            }
+
+            if (_synthesizedValidators.TryGetValue(mt, out var validator))
+            {
+                return "global::" + validator.Namespace + "." + validator.Name;
+            }
+
+            var membersToValidate = GetMembersToValidate(mt, true);
+            if (membersToValidate.Count == 0)
+            {
+                // this type lacks any eligible members
+                Diag(DiagDescriptors.NoEligibleMember, member.GetLocation(), mt.ToString(), member.ToString());
+                return null;
+            }
+
+            var model = new ValidatedModel(
+                GetFQN(mt),
+                mt.Name,
+                false,
+                membersToValidate);
+
+            var validatorTypeName = "__" + mt.Name + "Validator__";
+
+            var result = new ValidatorType(
+                mt.ContainingNamespace.IsGlobalNamespace ? string.Empty : mt.ContainingNamespace.ToString(),
+                validatorTypeName,
+                validatorTypeName,
+                "class",
+                new List<string>(),
+                true,
+                new[] { model });
+
+            _synthesizedValidators[mt] = result;
+            return "global::" + (result.Namespace.Length > 0 ? result.Namespace + "." + result.Name : result.Name);
+        }
+
+        private bool ConvertTo(ITypeSymbol source, ITypeSymbol dest)
+        {
+            var conversion = _compilation.ClassifyConversion(source, dest);
+            return conversion.IsReference && conversion.IsImplicit;
+        }
+
+        private bool ModelSelfValidates(ITypeSymbol modelType)
+        {
+            foreach (var implementingInterface in modelType.AllInterfaces)
+            {
+                if (SymbolEqualityComparer.Default.Equals(implementingInterface.OriginalDefinition, _symbolHolder.IValidatableObjectSymbol))
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        private List<ITypeSymbol> GetModelTypes(ITypeSymbol validatorType)
+        {
+            var result = new List<ITypeSymbol>();
+            foreach (var implementingInterface in validatorType.AllInterfaces)
+            {
+                if (SymbolEqualityComparer.Default.Equals(implementingInterface.OriginalDefinition, _symbolHolder.ValidateOptionsSymbol))
+                {
+                    result.Add(implementingInterface.TypeArguments.First());
+                }
+            }
+
+            return result;
+        }
+
+        private bool CanValidate(ITypeSymbol validatorType, ISymbol modelType)
+        {
+            foreach (var implementingInterface in validatorType.AllInterfaces)
+            {
+                if (SymbolEqualityComparer.Default.Equals(implementingInterface.OriginalDefinition, _symbolHolder.ValidateOptionsSymbol))
+                {
+                    var t = implementingInterface.TypeArguments.First();
+                    if (SymbolEqualityComparer.Default.Equals(modelType, t))
+                    {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        private string GetArgumentExpression(ITypeSymbol type, object? value)
+        {
+            if (value == null)
+            {
+                return "null";
+            }
+
+            if (type.SpecialType == SpecialType.System_Boolean)
+            {
+                return (bool)value ? "true" : "false";
+            }
+
+            if (SymbolEqualityComparer.Default.Equals(type, _symbolHolder.TypeSymbol) &&
+                value is INamedTypeSymbol sym)
+            {
+                return $"typeof({sym.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)})";
+            }
+
+            if (type.SpecialType == SpecialType.System_String)
+            {
+                return $@"""{EscapeString(value.ToString())}""";
+            }
+
+            if (type.SpecialType == SpecialType.System_Char)
+            {
+                return $@"'{EscapeString(value.ToString())}'";
+            }
+
+            return $"({type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}){Convert.ToString(value, CultureInfo.InvariantCulture)}";
+        }
+
+        private static readonly char[] _specialChars = { '\n', '\r', '"', '\\' };
+
+        private static string EscapeString(string s)
+        {
+            int index = s.IndexOfAny(_specialChars);
+            if (index < 0)
+            {
+                return s;
+            }
+
+            var sb = new StringBuilder(s.Length);
+            _ = sb.Append(s, 0, index);
+
+            while (index < s.Length)
+            {
+                _ = s[index] switch
+                {
+                    '\n' => sb.Append("\\n"),
+                    '\r' => sb.Append("\\r"),
+                    '"' => sb.Append("\\\""),
+                    '\\' => sb.Append("\\\\"),
+                    var other => sb.Append(other),
+                };
+
+                index++;
+            }
+
+            return sb.ToString();
+        }
+
+        private void Diag(DiagnosticDescriptor desc, Location? location)
+        {
+            _reportDiagnostic(Diagnostic.Create(desc, location, Array.Empty<object?>()));
+        }
+
+        private void Diag(DiagnosticDescriptor desc, Location? location, params object?[]? messageArgs)
+        {
+            _reportDiagnostic(Diagnostic.Create(desc, location, messageArgs));
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/ParserUtilities.cs b/src/libraries/Microsoft.Extensions.Options/gen/ParserUtilities.cs
new file mode 100644 (file)
index 0000000..d79ad4c
--- /dev/null
@@ -0,0 +1,75 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Linq;
+using System.Threading;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Microsoft.Extensions.Options.Generators
+{
+    internal static class ParserUtilities
+    {
+        internal static AttributeData? GetSymbolAttributeAnnotationOrDefault(ISymbol? attribute, ISymbol symbol)
+        {
+            if (attribute is null)
+            {
+                return null;
+            }
+
+            var attrs = symbol.GetAttributes();
+            foreach (var item in attrs)
+            {
+                if (SymbolEqualityComparer.Default.Equals(attribute, item.AttributeClass) && item.AttributeConstructor != null)
+                {
+                    return item;
+                }
+            }
+
+            return null;
+        }
+
+        internal static bool PropertyHasModifier(ISymbol property, SyntaxKind modifierToSearch, CancellationToken token)
+            => property
+                .DeclaringSyntaxReferences
+                .Any(x =>
+                    x.GetSyntax(token) is PropertyDeclarationSyntax syntax &&
+                    syntax.Modifiers.Any(m => m.IsKind(modifierToSearch)));
+
+        internal static Location? GetLocation(this ISymbol symbol)
+        {
+            if (symbol is null)
+            {
+                return null;
+            }
+
+            return symbol.Locations.IsDefaultOrEmpty
+                ? null
+                : symbol.Locations[0];
+        }
+
+        internal static bool IsBaseOrIdentity(ITypeSymbol source, ITypeSymbol dest, Compilation comp)
+        {
+            var conversion = comp.ClassifyConversion(source, dest);
+            return conversion.IsIdentity || (conversion.IsReference && conversion.IsImplicit);
+        }
+
+        internal static bool ImplementsInterface(this ITypeSymbol type, ITypeSymbol interfaceType)
+        {
+            foreach (var iface in type.AllInterfaces)
+            {
+                if (SymbolEqualityComparer.Default.Equals(interfaceType, iface))
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        // Check if parameter has either simplified (i.e. "int?") or explicit (Nullable<int>) nullable type declaration:
+        internal static bool IsNullableOfT(this ITypeSymbol type)
+            => type.SpecialType == SpecialType.System_Nullable_T || type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T;
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/Strings.resx b/src/libraries/Microsoft.Extensions.Options/gen/Resources/Strings.resx
new file mode 100644 (file)
index 0000000..021f345
--- /dev/null
@@ -0,0 +1,198 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!--
+    Microsoft ResX Schema
+
+    Version 2.0
+
+    The primary goals of this format is to allow a simple XML format
+    that is mostly human readable. The generation and parsing of the
+    various data types are done through the TypeConverter classes
+    associated with the data types.
+
+    Example:
+
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+
+    There are any number of "resheader" rows that contain simple
+    name/value pairs.
+
+    Each data row contains a name, and value. The row also contains a
+    type or mimetype. Type corresponds to a .NET class that support
+    text/value conversion through the TypeConverter architecture.
+    Classes that don't support this are serialized and stored with the
+    mimetype set.
+
+    The mimetype is used for serialized objects, and tells the
+    ResXResourceReader how to depersist the object. This is currently not
+    extensible. For a given mimetype the value must be set accordingly:
+
+    Note - application/x-microsoft.net.object.binary.base64 is the format
+    that the ResXResourceWriter will generate, however the reader can
+    read any of the formats listed below.
+
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="AlreadyImplementsValidateMethodMessage" xml:space="preserve">
+    <value>Type {0} already implements the Validate method.</value>
+  </data>
+  <data name="AlreadyImplementsValidateMethodTitle" xml:space="preserve">
+    <value>A type already includes an implementation of the 'Validate' method.</value>
+  </data>
+  <data name="CantBeStaticClassMessage" xml:space="preserve">
+    <value>[OptionsValidator] cannot be applied to static class {0}.</value>
+  </data>
+  <data name="CantBeStaticClassTitle" xml:space="preserve">
+    <value>'OptionsValidatorAttribute' can't be applied to a static class.</value>
+  </data>
+  <data name="CantUseWithGenericTypesMessage" xml:space="preserve">
+    <value>Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</value>
+  </data>
+  <data name="CantUseWithGenericTypesTitle" xml:space="preserve">
+    <value>Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</value>
+  </data>
+  <data name="CircularTypeReferencesMessage" xml:space="preserve">
+    <value>There is a circular type reference involving type {0} preventing it from being used for static validation.</value>
+  </data>
+  <data name="CircularTypeReferencesTitle" xml:space="preserve">
+    <value>Unsupported circular references in model types.</value>
+  </data>
+  <data name="DoesntImplementIValidateOptionsMessage" xml:space="preserve">
+    <value>Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</value>
+  </data>
+  <data name="DoesntImplementIValidateOptionsTitle" xml:space="preserve">
+    <value>A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</value>
+  </data>
+  <data name="MemberIsInaccessibleMessage" xml:space="preserve">
+    <value>Can't apply validation attributes to private field or property {0}.</value>
+  </data>
+  <data name="MemberIsInaccessibleTitle" xml:space="preserve">
+    <value>Can't validate private fields or properties.</value>
+  </data>
+  <data name="NoEligibleMemberMessage" xml:space="preserve">
+    <value>Type {0} has no fields or properties to validate, referenced from member {1}.</value>
+  </data>
+  <data name="NoEligibleMembersFromValidatorMessage" xml:space="preserve">
+    <value>Type {0} has no fields or properties to validate, referenced by type {1}.</value>
+  </data>
+  <data name="NoEligibleMembersFromValidatorTitle" xml:space="preserve">
+    <value>A type has no fields or properties to validate.</value>
+  </data>
+  <data name="NoEligibleMemberTitle" xml:space="preserve">
+    <value>A member type has no fields or properties to validate.</value>
+  </data>
+  <data name="NotEnumerableTypeMessage" xml:space="preserve">
+    <value>[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</value>
+  </data>
+  <data name="NotEnumerableTypeTitle" xml:space="preserve">
+    <value>Member type is not enumerable.</value>
+  </data>
+  <data name="NullValidatorTypeMessage" xml:space="preserve">
+    <value>Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</value>
+  </data>
+  <data name="NullValidatorTypeTitle" xml:space="preserve">
+    <value>Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</value>
+  </data>
+  <data name="PotentiallyMissingEnumerableValidationMessage" xml:space="preserve">
+    <value>Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</value>
+  </data>
+  <data name="PotentiallyMissingEnumerableValidationTitle" xml:space="preserve">
+    <value>Member potentially missing enumerable validation.</value>
+  </data>
+  <data name="PotentiallyMissingTransitiveValidationMessage" xml:space="preserve">
+    <value>Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</value>
+  </data>
+  <data name="PotentiallyMissingTransitiveValidationTitle" xml:space="preserve">
+    <value>Member potentially missing transitive validation.</value>
+  </data>
+  <data name="ValidatorsNeedSimpleConstructorMessage" xml:space="preserve">
+    <value>Validator type {0} doesn't have a parameterless constructor.</value>
+  </data>
+  <data name="ValidatorsNeedSimpleConstructorTitle" xml:space="preserve">
+    <value>Validators used for transitive or enumerable validation must have a constructor with no parameters.</value>
+  </data>
+</root>
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.cs.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.cs.xlf
new file mode 100644 (file)
index 0000000..a276198
--- /dev/null
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
+  <file datatype="xml" source-language="en" target-language="cs" original="../Strings.resx">
+    <body>
+      <trans-unit id="AlreadyImplementsValidateMethodMessage">
+        <source>Type {0} already implements the Validate method.</source>
+        <target state="new">Type {0} already implements the Validate method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="AlreadyImplementsValidateMethodTitle">
+        <source>A type already includes an implementation of the 'Validate' method.</source>
+        <target state="new">A type already includes an implementation of the 'Validate' method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassMessage">
+        <source>[OptionsValidator] cannot be applied to static class {0}.</source>
+        <target state="new">[OptionsValidator] cannot be applied to static class {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassTitle">
+        <source>'OptionsValidatorAttribute' can't be applied to a static class.</source>
+        <target state="new">'OptionsValidatorAttribute' can't be applied to a static class.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesMessage">
+        <source>Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</source>
+        <target state="new">Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesTitle">
+        <source>Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</source>
+        <target state="new">Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesMessage">
+        <source>There is a circular type reference involving type {0} preventing it from being used for static validation.</source>
+        <target state="new">There is a circular type reference involving type {0} preventing it from being used for static validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesTitle">
+        <source>Unsupported circular references in model types.</source>
+        <target state="new">Unsupported circular references in model types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsMessage">
+        <source>Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</source>
+        <target state="new">Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsTitle">
+        <source>A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</source>
+        <target state="new">A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleMessage">
+        <source>Can't apply validation attributes to private field or property {0}.</source>
+        <target state="new">Can't apply validation attributes to private field or property {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleTitle">
+        <source>Can't validate private fields or properties.</source>
+        <target state="new">Can't validate private fields or properties.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberMessage">
+        <source>Type {0} has no fields or properties to validate, referenced from member {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced from member {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberTitle">
+        <source>A member type has no fields or properties to validate.</source>
+        <target state="new">A member type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorMessage">
+        <source>Type {0} has no fields or properties to validate, referenced by type {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced by type {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorTitle">
+        <source>A type has no fields or properties to validate.</source>
+        <target state="new">A type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeMessage">
+        <source>[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</source>
+        <target state="new">[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeTitle">
+        <source>Member type is not enumerable.</source>
+        <target state="new">Member type is not enumerable.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeMessage">
+        <source>Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</source>
+        <target state="new">Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeTitle">
+        <source>Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</source>
+        <target state="new">Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationTitle">
+        <source>Member potentially missing enumerable validation.</source>
+        <target state="new">Member potentially missing enumerable validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationTitle">
+        <source>Member potentially missing transitive validation.</source>
+        <target state="new">Member potentially missing transitive validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
+        <source>Validator type {0} doesn't have a parameterless constructor.</source>
+        <target state="new">Validator type {0} doesn't have a parameterless constructor.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorTitle">
+        <source>Validators used for transitive or enumerable validation must have a constructor with no parameters.</source>
+        <target state="new">Validators used for transitive or enumerable validation must have a constructor with no parameters.</target>
+        <note />
+      </trans-unit>
+    </body>
+  </file>
+</xliff>
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.de.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.de.xlf
new file mode 100644 (file)
index 0000000..1a25435
--- /dev/null
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
+  <file datatype="xml" source-language="en" target-language="de" original="../Strings.resx">
+    <body>
+      <trans-unit id="AlreadyImplementsValidateMethodMessage">
+        <source>Type {0} already implements the Validate method.</source>
+        <target state="new">Type {0} already implements the Validate method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="AlreadyImplementsValidateMethodTitle">
+        <source>A type already includes an implementation of the 'Validate' method.</source>
+        <target state="new">A type already includes an implementation of the 'Validate' method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassMessage">
+        <source>[OptionsValidator] cannot be applied to static class {0}.</source>
+        <target state="new">[OptionsValidator] cannot be applied to static class {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassTitle">
+        <source>'OptionsValidatorAttribute' can't be applied to a static class.</source>
+        <target state="new">'OptionsValidatorAttribute' can't be applied to a static class.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesMessage">
+        <source>Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</source>
+        <target state="new">Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesTitle">
+        <source>Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</source>
+        <target state="new">Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesMessage">
+        <source>There is a circular type reference involving type {0} preventing it from being used for static validation.</source>
+        <target state="new">There is a circular type reference involving type {0} preventing it from being used for static validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesTitle">
+        <source>Unsupported circular references in model types.</source>
+        <target state="new">Unsupported circular references in model types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsMessage">
+        <source>Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</source>
+        <target state="new">Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsTitle">
+        <source>A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</source>
+        <target state="new">A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleMessage">
+        <source>Can't apply validation attributes to private field or property {0}.</source>
+        <target state="new">Can't apply validation attributes to private field or property {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleTitle">
+        <source>Can't validate private fields or properties.</source>
+        <target state="new">Can't validate private fields or properties.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberMessage">
+        <source>Type {0} has no fields or properties to validate, referenced from member {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced from member {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberTitle">
+        <source>A member type has no fields or properties to validate.</source>
+        <target state="new">A member type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorMessage">
+        <source>Type {0} has no fields or properties to validate, referenced by type {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced by type {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorTitle">
+        <source>A type has no fields or properties to validate.</source>
+        <target state="new">A type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeMessage">
+        <source>[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</source>
+        <target state="new">[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeTitle">
+        <source>Member type is not enumerable.</source>
+        <target state="new">Member type is not enumerable.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeMessage">
+        <source>Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</source>
+        <target state="new">Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeTitle">
+        <source>Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</source>
+        <target state="new">Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationTitle">
+        <source>Member potentially missing enumerable validation.</source>
+        <target state="new">Member potentially missing enumerable validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationTitle">
+        <source>Member potentially missing transitive validation.</source>
+        <target state="new">Member potentially missing transitive validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
+        <source>Validator type {0} doesn't have a parameterless constructor.</source>
+        <target state="new">Validator type {0} doesn't have a parameterless constructor.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorTitle">
+        <source>Validators used for transitive or enumerable validation must have a constructor with no parameters.</source>
+        <target state="new">Validators used for transitive or enumerable validation must have a constructor with no parameters.</target>
+        <note />
+      </trans-unit>
+    </body>
+  </file>
+</xliff>
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.es.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.es.xlf
new file mode 100644 (file)
index 0000000..ba26cf8
--- /dev/null
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
+  <file datatype="xml" source-language="en" target-language="es" original="../Strings.resx">
+    <body>
+      <trans-unit id="AlreadyImplementsValidateMethodMessage">
+        <source>Type {0} already implements the Validate method.</source>
+        <target state="new">Type {0} already implements the Validate method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="AlreadyImplementsValidateMethodTitle">
+        <source>A type already includes an implementation of the 'Validate' method.</source>
+        <target state="new">A type already includes an implementation of the 'Validate' method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassMessage">
+        <source>[OptionsValidator] cannot be applied to static class {0}.</source>
+        <target state="new">[OptionsValidator] cannot be applied to static class {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassTitle">
+        <source>'OptionsValidatorAttribute' can't be applied to a static class.</source>
+        <target state="new">'OptionsValidatorAttribute' can't be applied to a static class.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesMessage">
+        <source>Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</source>
+        <target state="new">Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesTitle">
+        <source>Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</source>
+        <target state="new">Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesMessage">
+        <source>There is a circular type reference involving type {0} preventing it from being used for static validation.</source>
+        <target state="new">There is a circular type reference involving type {0} preventing it from being used for static validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesTitle">
+        <source>Unsupported circular references in model types.</source>
+        <target state="new">Unsupported circular references in model types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsMessage">
+        <source>Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</source>
+        <target state="new">Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsTitle">
+        <source>A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</source>
+        <target state="new">A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleMessage">
+        <source>Can't apply validation attributes to private field or property {0}.</source>
+        <target state="new">Can't apply validation attributes to private field or property {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleTitle">
+        <source>Can't validate private fields or properties.</source>
+        <target state="new">Can't validate private fields or properties.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberMessage">
+        <source>Type {0} has no fields or properties to validate, referenced from member {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced from member {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberTitle">
+        <source>A member type has no fields or properties to validate.</source>
+        <target state="new">A member type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorMessage">
+        <source>Type {0} has no fields or properties to validate, referenced by type {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced by type {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorTitle">
+        <source>A type has no fields or properties to validate.</source>
+        <target state="new">A type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeMessage">
+        <source>[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</source>
+        <target state="new">[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeTitle">
+        <source>Member type is not enumerable.</source>
+        <target state="new">Member type is not enumerable.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeMessage">
+        <source>Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</source>
+        <target state="new">Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeTitle">
+        <source>Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</source>
+        <target state="new">Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationTitle">
+        <source>Member potentially missing enumerable validation.</source>
+        <target state="new">Member potentially missing enumerable validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationTitle">
+        <source>Member potentially missing transitive validation.</source>
+        <target state="new">Member potentially missing transitive validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
+        <source>Validator type {0} doesn't have a parameterless constructor.</source>
+        <target state="new">Validator type {0} doesn't have a parameterless constructor.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorTitle">
+        <source>Validators used for transitive or enumerable validation must have a constructor with no parameters.</source>
+        <target state="new">Validators used for transitive or enumerable validation must have a constructor with no parameters.</target>
+        <note />
+      </trans-unit>
+    </body>
+  </file>
+</xliff>
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.fr.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.fr.xlf
new file mode 100644 (file)
index 0000000..748bf1c
--- /dev/null
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
+  <file datatype="xml" source-language="en" target-language="fr" original="../Strings.resx">
+    <body>
+      <trans-unit id="AlreadyImplementsValidateMethodMessage">
+        <source>Type {0} already implements the Validate method.</source>
+        <target state="new">Type {0} already implements the Validate method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="AlreadyImplementsValidateMethodTitle">
+        <source>A type already includes an implementation of the 'Validate' method.</source>
+        <target state="new">A type already includes an implementation of the 'Validate' method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassMessage">
+        <source>[OptionsValidator] cannot be applied to static class {0}.</source>
+        <target state="new">[OptionsValidator] cannot be applied to static class {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassTitle">
+        <source>'OptionsValidatorAttribute' can't be applied to a static class.</source>
+        <target state="new">'OptionsValidatorAttribute' can't be applied to a static class.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesMessage">
+        <source>Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</source>
+        <target state="new">Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesTitle">
+        <source>Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</source>
+        <target state="new">Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesMessage">
+        <source>There is a circular type reference involving type {0} preventing it from being used for static validation.</source>
+        <target state="new">There is a circular type reference involving type {0} preventing it from being used for static validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesTitle">
+        <source>Unsupported circular references in model types.</source>
+        <target state="new">Unsupported circular references in model types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsMessage">
+        <source>Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</source>
+        <target state="new">Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsTitle">
+        <source>A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</source>
+        <target state="new">A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleMessage">
+        <source>Can't apply validation attributes to private field or property {0}.</source>
+        <target state="new">Can't apply validation attributes to private field or property {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleTitle">
+        <source>Can't validate private fields or properties.</source>
+        <target state="new">Can't validate private fields or properties.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberMessage">
+        <source>Type {0} has no fields or properties to validate, referenced from member {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced from member {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberTitle">
+        <source>A member type has no fields or properties to validate.</source>
+        <target state="new">A member type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorMessage">
+        <source>Type {0} has no fields or properties to validate, referenced by type {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced by type {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorTitle">
+        <source>A type has no fields or properties to validate.</source>
+        <target state="new">A type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeMessage">
+        <source>[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</source>
+        <target state="new">[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeTitle">
+        <source>Member type is not enumerable.</source>
+        <target state="new">Member type is not enumerable.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeMessage">
+        <source>Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</source>
+        <target state="new">Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeTitle">
+        <source>Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</source>
+        <target state="new">Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationTitle">
+        <source>Member potentially missing enumerable validation.</source>
+        <target state="new">Member potentially missing enumerable validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationTitle">
+        <source>Member potentially missing transitive validation.</source>
+        <target state="new">Member potentially missing transitive validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
+        <source>Validator type {0} doesn't have a parameterless constructor.</source>
+        <target state="new">Validator type {0} doesn't have a parameterless constructor.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorTitle">
+        <source>Validators used for transitive or enumerable validation must have a constructor with no parameters.</source>
+        <target state="new">Validators used for transitive or enumerable validation must have a constructor with no parameters.</target>
+        <note />
+      </trans-unit>
+    </body>
+  </file>
+</xliff>
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.it.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.it.xlf
new file mode 100644 (file)
index 0000000..386b07a
--- /dev/null
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
+  <file datatype="xml" source-language="en" target-language="it" original="../Strings.resx">
+    <body>
+      <trans-unit id="AlreadyImplementsValidateMethodMessage">
+        <source>Type {0} already implements the Validate method.</source>
+        <target state="new">Type {0} already implements the Validate method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="AlreadyImplementsValidateMethodTitle">
+        <source>A type already includes an implementation of the 'Validate' method.</source>
+        <target state="new">A type already includes an implementation of the 'Validate' method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassMessage">
+        <source>[OptionsValidator] cannot be applied to static class {0}.</source>
+        <target state="new">[OptionsValidator] cannot be applied to static class {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassTitle">
+        <source>'OptionsValidatorAttribute' can't be applied to a static class.</source>
+        <target state="new">'OptionsValidatorAttribute' can't be applied to a static class.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesMessage">
+        <source>Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</source>
+        <target state="new">Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesTitle">
+        <source>Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</source>
+        <target state="new">Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesMessage">
+        <source>There is a circular type reference involving type {0} preventing it from being used for static validation.</source>
+        <target state="new">There is a circular type reference involving type {0} preventing it from being used for static validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesTitle">
+        <source>Unsupported circular references in model types.</source>
+        <target state="new">Unsupported circular references in model types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsMessage">
+        <source>Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</source>
+        <target state="new">Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsTitle">
+        <source>A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</source>
+        <target state="new">A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleMessage">
+        <source>Can't apply validation attributes to private field or property {0}.</source>
+        <target state="new">Can't apply validation attributes to private field or property {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleTitle">
+        <source>Can't validate private fields or properties.</source>
+        <target state="new">Can't validate private fields or properties.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberMessage">
+        <source>Type {0} has no fields or properties to validate, referenced from member {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced from member {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberTitle">
+        <source>A member type has no fields or properties to validate.</source>
+        <target state="new">A member type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorMessage">
+        <source>Type {0} has no fields or properties to validate, referenced by type {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced by type {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorTitle">
+        <source>A type has no fields or properties to validate.</source>
+        <target state="new">A type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeMessage">
+        <source>[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</source>
+        <target state="new">[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeTitle">
+        <source>Member type is not enumerable.</source>
+        <target state="new">Member type is not enumerable.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeMessage">
+        <source>Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</source>
+        <target state="new">Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeTitle">
+        <source>Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</source>
+        <target state="new">Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationTitle">
+        <source>Member potentially missing enumerable validation.</source>
+        <target state="new">Member potentially missing enumerable validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationTitle">
+        <source>Member potentially missing transitive validation.</source>
+        <target state="new">Member potentially missing transitive validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
+        <source>Validator type {0} doesn't have a parameterless constructor.</source>
+        <target state="new">Validator type {0} doesn't have a parameterless constructor.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorTitle">
+        <source>Validators used for transitive or enumerable validation must have a constructor with no parameters.</source>
+        <target state="new">Validators used for transitive or enumerable validation must have a constructor with no parameters.</target>
+        <note />
+      </trans-unit>
+    </body>
+  </file>
+</xliff>
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ja.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ja.xlf
new file mode 100644 (file)
index 0000000..1f4ccf0
--- /dev/null
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
+  <file datatype="xml" source-language="en" target-language="ja" original="../Strings.resx">
+    <body>
+      <trans-unit id="AlreadyImplementsValidateMethodMessage">
+        <source>Type {0} already implements the Validate method.</source>
+        <target state="new">Type {0} already implements the Validate method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="AlreadyImplementsValidateMethodTitle">
+        <source>A type already includes an implementation of the 'Validate' method.</source>
+        <target state="new">A type already includes an implementation of the 'Validate' method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassMessage">
+        <source>[OptionsValidator] cannot be applied to static class {0}.</source>
+        <target state="new">[OptionsValidator] cannot be applied to static class {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassTitle">
+        <source>'OptionsValidatorAttribute' can't be applied to a static class.</source>
+        <target state="new">'OptionsValidatorAttribute' can't be applied to a static class.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesMessage">
+        <source>Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</source>
+        <target state="new">Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesTitle">
+        <source>Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</source>
+        <target state="new">Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesMessage">
+        <source>There is a circular type reference involving type {0} preventing it from being used for static validation.</source>
+        <target state="new">There is a circular type reference involving type {0} preventing it from being used for static validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesTitle">
+        <source>Unsupported circular references in model types.</source>
+        <target state="new">Unsupported circular references in model types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsMessage">
+        <source>Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</source>
+        <target state="new">Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsTitle">
+        <source>A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</source>
+        <target state="new">A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleMessage">
+        <source>Can't apply validation attributes to private field or property {0}.</source>
+        <target state="new">Can't apply validation attributes to private field or property {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleTitle">
+        <source>Can't validate private fields or properties.</source>
+        <target state="new">Can't validate private fields or properties.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberMessage">
+        <source>Type {0} has no fields or properties to validate, referenced from member {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced from member {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberTitle">
+        <source>A member type has no fields or properties to validate.</source>
+        <target state="new">A member type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorMessage">
+        <source>Type {0} has no fields or properties to validate, referenced by type {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced by type {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorTitle">
+        <source>A type has no fields or properties to validate.</source>
+        <target state="new">A type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeMessage">
+        <source>[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</source>
+        <target state="new">[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeTitle">
+        <source>Member type is not enumerable.</source>
+        <target state="new">Member type is not enumerable.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeMessage">
+        <source>Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</source>
+        <target state="new">Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeTitle">
+        <source>Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</source>
+        <target state="new">Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationTitle">
+        <source>Member potentially missing enumerable validation.</source>
+        <target state="new">Member potentially missing enumerable validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationTitle">
+        <source>Member potentially missing transitive validation.</source>
+        <target state="new">Member potentially missing transitive validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
+        <source>Validator type {0} doesn't have a parameterless constructor.</source>
+        <target state="new">Validator type {0} doesn't have a parameterless constructor.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorTitle">
+        <source>Validators used for transitive or enumerable validation must have a constructor with no parameters.</source>
+        <target state="new">Validators used for transitive or enumerable validation must have a constructor with no parameters.</target>
+        <note />
+      </trans-unit>
+    </body>
+  </file>
+</xliff>
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ko.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ko.xlf
new file mode 100644 (file)
index 0000000..283d22e
--- /dev/null
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
+  <file datatype="xml" source-language="en" target-language="ko" original="../Strings.resx">
+    <body>
+      <trans-unit id="AlreadyImplementsValidateMethodMessage">
+        <source>Type {0} already implements the Validate method.</source>
+        <target state="new">Type {0} already implements the Validate method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="AlreadyImplementsValidateMethodTitle">
+        <source>A type already includes an implementation of the 'Validate' method.</source>
+        <target state="new">A type already includes an implementation of the 'Validate' method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassMessage">
+        <source>[OptionsValidator] cannot be applied to static class {0}.</source>
+        <target state="new">[OptionsValidator] cannot be applied to static class {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassTitle">
+        <source>'OptionsValidatorAttribute' can't be applied to a static class.</source>
+        <target state="new">'OptionsValidatorAttribute' can't be applied to a static class.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesMessage">
+        <source>Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</source>
+        <target state="new">Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesTitle">
+        <source>Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</source>
+        <target state="new">Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesMessage">
+        <source>There is a circular type reference involving type {0} preventing it from being used for static validation.</source>
+        <target state="new">There is a circular type reference involving type {0} preventing it from being used for static validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesTitle">
+        <source>Unsupported circular references in model types.</source>
+        <target state="new">Unsupported circular references in model types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsMessage">
+        <source>Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</source>
+        <target state="new">Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsTitle">
+        <source>A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</source>
+        <target state="new">A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleMessage">
+        <source>Can't apply validation attributes to private field or property {0}.</source>
+        <target state="new">Can't apply validation attributes to private field or property {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleTitle">
+        <source>Can't validate private fields or properties.</source>
+        <target state="new">Can't validate private fields or properties.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberMessage">
+        <source>Type {0} has no fields or properties to validate, referenced from member {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced from member {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberTitle">
+        <source>A member type has no fields or properties to validate.</source>
+        <target state="new">A member type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorMessage">
+        <source>Type {0} has no fields or properties to validate, referenced by type {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced by type {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorTitle">
+        <source>A type has no fields or properties to validate.</source>
+        <target state="new">A type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeMessage">
+        <source>[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</source>
+        <target state="new">[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeTitle">
+        <source>Member type is not enumerable.</source>
+        <target state="new">Member type is not enumerable.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeMessage">
+        <source>Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</source>
+        <target state="new">Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeTitle">
+        <source>Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</source>
+        <target state="new">Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationTitle">
+        <source>Member potentially missing enumerable validation.</source>
+        <target state="new">Member potentially missing enumerable validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationTitle">
+        <source>Member potentially missing transitive validation.</source>
+        <target state="new">Member potentially missing transitive validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
+        <source>Validator type {0} doesn't have a parameterless constructor.</source>
+        <target state="new">Validator type {0} doesn't have a parameterless constructor.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorTitle">
+        <source>Validators used for transitive or enumerable validation must have a constructor with no parameters.</source>
+        <target state="new">Validators used for transitive or enumerable validation must have a constructor with no parameters.</target>
+        <note />
+      </trans-unit>
+    </body>
+  </file>
+</xliff>
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.pl.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.pl.xlf
new file mode 100644 (file)
index 0000000..cfa99a0
--- /dev/null
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
+  <file datatype="xml" source-language="en" target-language="pl" original="../Strings.resx">
+    <body>
+      <trans-unit id="AlreadyImplementsValidateMethodMessage">
+        <source>Type {0} already implements the Validate method.</source>
+        <target state="new">Type {0} already implements the Validate method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="AlreadyImplementsValidateMethodTitle">
+        <source>A type already includes an implementation of the 'Validate' method.</source>
+        <target state="new">A type already includes an implementation of the 'Validate' method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassMessage">
+        <source>[OptionsValidator] cannot be applied to static class {0}.</source>
+        <target state="new">[OptionsValidator] cannot be applied to static class {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassTitle">
+        <source>'OptionsValidatorAttribute' can't be applied to a static class.</source>
+        <target state="new">'OptionsValidatorAttribute' can't be applied to a static class.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesMessage">
+        <source>Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</source>
+        <target state="new">Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesTitle">
+        <source>Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</source>
+        <target state="new">Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesMessage">
+        <source>There is a circular type reference involving type {0} preventing it from being used for static validation.</source>
+        <target state="new">There is a circular type reference involving type {0} preventing it from being used for static validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesTitle">
+        <source>Unsupported circular references in model types.</source>
+        <target state="new">Unsupported circular references in model types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsMessage">
+        <source>Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</source>
+        <target state="new">Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsTitle">
+        <source>A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</source>
+        <target state="new">A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleMessage">
+        <source>Can't apply validation attributes to private field or property {0}.</source>
+        <target state="new">Can't apply validation attributes to private field or property {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleTitle">
+        <source>Can't validate private fields or properties.</source>
+        <target state="new">Can't validate private fields or properties.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberMessage">
+        <source>Type {0} has no fields or properties to validate, referenced from member {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced from member {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberTitle">
+        <source>A member type has no fields or properties to validate.</source>
+        <target state="new">A member type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorMessage">
+        <source>Type {0} has no fields or properties to validate, referenced by type {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced by type {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorTitle">
+        <source>A type has no fields or properties to validate.</source>
+        <target state="new">A type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeMessage">
+        <source>[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</source>
+        <target state="new">[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeTitle">
+        <source>Member type is not enumerable.</source>
+        <target state="new">Member type is not enumerable.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeMessage">
+        <source>Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</source>
+        <target state="new">Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeTitle">
+        <source>Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</source>
+        <target state="new">Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationTitle">
+        <source>Member potentially missing enumerable validation.</source>
+        <target state="new">Member potentially missing enumerable validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationTitle">
+        <source>Member potentially missing transitive validation.</source>
+        <target state="new">Member potentially missing transitive validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
+        <source>Validator type {0} doesn't have a parameterless constructor.</source>
+        <target state="new">Validator type {0} doesn't have a parameterless constructor.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorTitle">
+        <source>Validators used for transitive or enumerable validation must have a constructor with no parameters.</source>
+        <target state="new">Validators used for transitive or enumerable validation must have a constructor with no parameters.</target>
+        <note />
+      </trans-unit>
+    </body>
+  </file>
+</xliff>
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.pt-BR.xlf
new file mode 100644 (file)
index 0000000..80eb423
--- /dev/null
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
+  <file datatype="xml" source-language="en" target-language="pt-BR" original="../Strings.resx">
+    <body>
+      <trans-unit id="AlreadyImplementsValidateMethodMessage">
+        <source>Type {0} already implements the Validate method.</source>
+        <target state="new">Type {0} already implements the Validate method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="AlreadyImplementsValidateMethodTitle">
+        <source>A type already includes an implementation of the 'Validate' method.</source>
+        <target state="new">A type already includes an implementation of the 'Validate' method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassMessage">
+        <source>[OptionsValidator] cannot be applied to static class {0}.</source>
+        <target state="new">[OptionsValidator] cannot be applied to static class {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassTitle">
+        <source>'OptionsValidatorAttribute' can't be applied to a static class.</source>
+        <target state="new">'OptionsValidatorAttribute' can't be applied to a static class.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesMessage">
+        <source>Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</source>
+        <target state="new">Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesTitle">
+        <source>Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</source>
+        <target state="new">Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesMessage">
+        <source>There is a circular type reference involving type {0} preventing it from being used for static validation.</source>
+        <target state="new">There is a circular type reference involving type {0} preventing it from being used for static validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesTitle">
+        <source>Unsupported circular references in model types.</source>
+        <target state="new">Unsupported circular references in model types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsMessage">
+        <source>Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</source>
+        <target state="new">Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsTitle">
+        <source>A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</source>
+        <target state="new">A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleMessage">
+        <source>Can't apply validation attributes to private field or property {0}.</source>
+        <target state="new">Can't apply validation attributes to private field or property {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleTitle">
+        <source>Can't validate private fields or properties.</source>
+        <target state="new">Can't validate private fields or properties.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberMessage">
+        <source>Type {0} has no fields or properties to validate, referenced from member {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced from member {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberTitle">
+        <source>A member type has no fields or properties to validate.</source>
+        <target state="new">A member type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorMessage">
+        <source>Type {0} has no fields or properties to validate, referenced by type {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced by type {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorTitle">
+        <source>A type has no fields or properties to validate.</source>
+        <target state="new">A type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeMessage">
+        <source>[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</source>
+        <target state="new">[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeTitle">
+        <source>Member type is not enumerable.</source>
+        <target state="new">Member type is not enumerable.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeMessage">
+        <source>Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</source>
+        <target state="new">Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeTitle">
+        <source>Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</source>
+        <target state="new">Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationTitle">
+        <source>Member potentially missing enumerable validation.</source>
+        <target state="new">Member potentially missing enumerable validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationTitle">
+        <source>Member potentially missing transitive validation.</source>
+        <target state="new">Member potentially missing transitive validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
+        <source>Validator type {0} doesn't have a parameterless constructor.</source>
+        <target state="new">Validator type {0} doesn't have a parameterless constructor.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorTitle">
+        <source>Validators used for transitive or enumerable validation must have a constructor with no parameters.</source>
+        <target state="new">Validators used for transitive or enumerable validation must have a constructor with no parameters.</target>
+        <note />
+      </trans-unit>
+    </body>
+  </file>
+</xliff>
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ru.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ru.xlf
new file mode 100644 (file)
index 0000000..7c01d66
--- /dev/null
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
+  <file datatype="xml" source-language="en" target-language="ru" original="../Strings.resx">
+    <body>
+      <trans-unit id="AlreadyImplementsValidateMethodMessage">
+        <source>Type {0} already implements the Validate method.</source>
+        <target state="new">Type {0} already implements the Validate method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="AlreadyImplementsValidateMethodTitle">
+        <source>A type already includes an implementation of the 'Validate' method.</source>
+        <target state="new">A type already includes an implementation of the 'Validate' method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassMessage">
+        <source>[OptionsValidator] cannot be applied to static class {0}.</source>
+        <target state="new">[OptionsValidator] cannot be applied to static class {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassTitle">
+        <source>'OptionsValidatorAttribute' can't be applied to a static class.</source>
+        <target state="new">'OptionsValidatorAttribute' can't be applied to a static class.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesMessage">
+        <source>Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</source>
+        <target state="new">Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesTitle">
+        <source>Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</source>
+        <target state="new">Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesMessage">
+        <source>There is a circular type reference involving type {0} preventing it from being used for static validation.</source>
+        <target state="new">There is a circular type reference involving type {0} preventing it from being used for static validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesTitle">
+        <source>Unsupported circular references in model types.</source>
+        <target state="new">Unsupported circular references in model types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsMessage">
+        <source>Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</source>
+        <target state="new">Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsTitle">
+        <source>A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</source>
+        <target state="new">A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleMessage">
+        <source>Can't apply validation attributes to private field or property {0}.</source>
+        <target state="new">Can't apply validation attributes to private field or property {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleTitle">
+        <source>Can't validate private fields or properties.</source>
+        <target state="new">Can't validate private fields or properties.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberMessage">
+        <source>Type {0} has no fields or properties to validate, referenced from member {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced from member {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberTitle">
+        <source>A member type has no fields or properties to validate.</source>
+        <target state="new">A member type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorMessage">
+        <source>Type {0} has no fields or properties to validate, referenced by type {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced by type {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorTitle">
+        <source>A type has no fields or properties to validate.</source>
+        <target state="new">A type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeMessage">
+        <source>[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</source>
+        <target state="new">[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeTitle">
+        <source>Member type is not enumerable.</source>
+        <target state="new">Member type is not enumerable.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeMessage">
+        <source>Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</source>
+        <target state="new">Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeTitle">
+        <source>Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</source>
+        <target state="new">Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationTitle">
+        <source>Member potentially missing enumerable validation.</source>
+        <target state="new">Member potentially missing enumerable validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationTitle">
+        <source>Member potentially missing transitive validation.</source>
+        <target state="new">Member potentially missing transitive validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
+        <source>Validator type {0} doesn't have a parameterless constructor.</source>
+        <target state="new">Validator type {0} doesn't have a parameterless constructor.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorTitle">
+        <source>Validators used for transitive or enumerable validation must have a constructor with no parameters.</source>
+        <target state="new">Validators used for transitive or enumerable validation must have a constructor with no parameters.</target>
+        <note />
+      </trans-unit>
+    </body>
+  </file>
+</xliff>
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.tr.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.tr.xlf
new file mode 100644 (file)
index 0000000..4c7a25e
--- /dev/null
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
+  <file datatype="xml" source-language="en" target-language="tr" original="../Strings.resx">
+    <body>
+      <trans-unit id="AlreadyImplementsValidateMethodMessage">
+        <source>Type {0} already implements the Validate method.</source>
+        <target state="new">Type {0} already implements the Validate method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="AlreadyImplementsValidateMethodTitle">
+        <source>A type already includes an implementation of the 'Validate' method.</source>
+        <target state="new">A type already includes an implementation of the 'Validate' method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassMessage">
+        <source>[OptionsValidator] cannot be applied to static class {0}.</source>
+        <target state="new">[OptionsValidator] cannot be applied to static class {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassTitle">
+        <source>'OptionsValidatorAttribute' can't be applied to a static class.</source>
+        <target state="new">'OptionsValidatorAttribute' can't be applied to a static class.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesMessage">
+        <source>Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</source>
+        <target state="new">Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesTitle">
+        <source>Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</source>
+        <target state="new">Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesMessage">
+        <source>There is a circular type reference involving type {0} preventing it from being used for static validation.</source>
+        <target state="new">There is a circular type reference involving type {0} preventing it from being used for static validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesTitle">
+        <source>Unsupported circular references in model types.</source>
+        <target state="new">Unsupported circular references in model types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsMessage">
+        <source>Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</source>
+        <target state="new">Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsTitle">
+        <source>A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</source>
+        <target state="new">A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleMessage">
+        <source>Can't apply validation attributes to private field or property {0}.</source>
+        <target state="new">Can't apply validation attributes to private field or property {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleTitle">
+        <source>Can't validate private fields or properties.</source>
+        <target state="new">Can't validate private fields or properties.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberMessage">
+        <source>Type {0} has no fields or properties to validate, referenced from member {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced from member {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberTitle">
+        <source>A member type has no fields or properties to validate.</source>
+        <target state="new">A member type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorMessage">
+        <source>Type {0} has no fields or properties to validate, referenced by type {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced by type {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorTitle">
+        <source>A type has no fields or properties to validate.</source>
+        <target state="new">A type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeMessage">
+        <source>[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</source>
+        <target state="new">[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeTitle">
+        <source>Member type is not enumerable.</source>
+        <target state="new">Member type is not enumerable.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeMessage">
+        <source>Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</source>
+        <target state="new">Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeTitle">
+        <source>Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</source>
+        <target state="new">Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationTitle">
+        <source>Member potentially missing enumerable validation.</source>
+        <target state="new">Member potentially missing enumerable validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationTitle">
+        <source>Member potentially missing transitive validation.</source>
+        <target state="new">Member potentially missing transitive validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
+        <source>Validator type {0} doesn't have a parameterless constructor.</source>
+        <target state="new">Validator type {0} doesn't have a parameterless constructor.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorTitle">
+        <source>Validators used for transitive or enumerable validation must have a constructor with no parameters.</source>
+        <target state="new">Validators used for transitive or enumerable validation must have a constructor with no parameters.</target>
+        <note />
+      </trans-unit>
+    </body>
+  </file>
+</xliff>
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.zh-Hans.xlf
new file mode 100644 (file)
index 0000000..46de6fa
--- /dev/null
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
+  <file datatype="xml" source-language="en" target-language="zh-Hans" original="../Strings.resx">
+    <body>
+      <trans-unit id="AlreadyImplementsValidateMethodMessage">
+        <source>Type {0} already implements the Validate method.</source>
+        <target state="new">Type {0} already implements the Validate method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="AlreadyImplementsValidateMethodTitle">
+        <source>A type already includes an implementation of the 'Validate' method.</source>
+        <target state="new">A type already includes an implementation of the 'Validate' method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassMessage">
+        <source>[OptionsValidator] cannot be applied to static class {0}.</source>
+        <target state="new">[OptionsValidator] cannot be applied to static class {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassTitle">
+        <source>'OptionsValidatorAttribute' can't be applied to a static class.</source>
+        <target state="new">'OptionsValidatorAttribute' can't be applied to a static class.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesMessage">
+        <source>Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</source>
+        <target state="new">Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesTitle">
+        <source>Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</source>
+        <target state="new">Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesMessage">
+        <source>There is a circular type reference involving type {0} preventing it from being used for static validation.</source>
+        <target state="new">There is a circular type reference involving type {0} preventing it from being used for static validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesTitle">
+        <source>Unsupported circular references in model types.</source>
+        <target state="new">Unsupported circular references in model types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsMessage">
+        <source>Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</source>
+        <target state="new">Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsTitle">
+        <source>A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</source>
+        <target state="new">A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleMessage">
+        <source>Can't apply validation attributes to private field or property {0}.</source>
+        <target state="new">Can't apply validation attributes to private field or property {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleTitle">
+        <source>Can't validate private fields or properties.</source>
+        <target state="new">Can't validate private fields or properties.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberMessage">
+        <source>Type {0} has no fields or properties to validate, referenced from member {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced from member {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberTitle">
+        <source>A member type has no fields or properties to validate.</source>
+        <target state="new">A member type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorMessage">
+        <source>Type {0} has no fields or properties to validate, referenced by type {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced by type {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorTitle">
+        <source>A type has no fields or properties to validate.</source>
+        <target state="new">A type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeMessage">
+        <source>[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</source>
+        <target state="new">[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeTitle">
+        <source>Member type is not enumerable.</source>
+        <target state="new">Member type is not enumerable.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeMessage">
+        <source>Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</source>
+        <target state="new">Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeTitle">
+        <source>Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</source>
+        <target state="new">Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationTitle">
+        <source>Member potentially missing enumerable validation.</source>
+        <target state="new">Member potentially missing enumerable validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationTitle">
+        <source>Member potentially missing transitive validation.</source>
+        <target state="new">Member potentially missing transitive validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
+        <source>Validator type {0} doesn't have a parameterless constructor.</source>
+        <target state="new">Validator type {0} doesn't have a parameterless constructor.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorTitle">
+        <source>Validators used for transitive or enumerable validation must have a constructor with no parameters.</source>
+        <target state="new">Validators used for transitive or enumerable validation must have a constructor with no parameters.</target>
+        <note />
+      </trans-unit>
+    </body>
+  </file>
+</xliff>
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.zh-Hant.xlf
new file mode 100644 (file)
index 0000000..3b7ddf5
--- /dev/null
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
+  <file datatype="xml" source-language="en" target-language="zh-Hant" original="../Strings.resx">
+    <body>
+      <trans-unit id="AlreadyImplementsValidateMethodMessage">
+        <source>Type {0} already implements the Validate method.</source>
+        <target state="new">Type {0} already implements the Validate method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="AlreadyImplementsValidateMethodTitle">
+        <source>A type already includes an implementation of the 'Validate' method.</source>
+        <target state="new">A type already includes an implementation of the 'Validate' method.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassMessage">
+        <source>[OptionsValidator] cannot be applied to static class {0}.</source>
+        <target state="new">[OptionsValidator] cannot be applied to static class {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantBeStaticClassTitle">
+        <source>'OptionsValidatorAttribute' can't be applied to a static class.</source>
+        <target state="new">'OptionsValidatorAttribute' can't be applied to a static class.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesMessage">
+        <source>Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</source>
+        <target state="new">Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CantUseWithGenericTypesTitle">
+        <source>Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</source>
+        <target state="new">Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesMessage">
+        <source>There is a circular type reference involving type {0} preventing it from being used for static validation.</source>
+        <target state="new">There is a circular type reference involving type {0} preventing it from being used for static validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="CircularTypeReferencesTitle">
+        <source>Unsupported circular references in model types.</source>
+        <target state="new">Unsupported circular references in model types.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsMessage">
+        <source>Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</source>
+        <target state="new">Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="DoesntImplementIValidateOptionsTitle">
+        <source>A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</source>
+        <target state="new">A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleMessage">
+        <source>Can't apply validation attributes to private field or property {0}.</source>
+        <target state="new">Can't apply validation attributes to private field or property {0}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="MemberIsInaccessibleTitle">
+        <source>Can't validate private fields or properties.</source>
+        <target state="new">Can't validate private fields or properties.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberMessage">
+        <source>Type {0} has no fields or properties to validate, referenced from member {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced from member {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMemberTitle">
+        <source>A member type has no fields or properties to validate.</source>
+        <target state="new">A member type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorMessage">
+        <source>Type {0} has no fields or properties to validate, referenced by type {1}.</source>
+        <target state="new">Type {0} has no fields or properties to validate, referenced by type {1}.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NoEligibleMembersFromValidatorTitle">
+        <source>A type has no fields or properties to validate.</source>
+        <target state="new">A type has no fields or properties to validate.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeMessage">
+        <source>[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</source>
+        <target state="new">[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NotEnumerableTypeTitle">
+        <source>Member type is not enumerable.</source>
+        <target state="new">Member type is not enumerable.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeMessage">
+        <source>Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</source>
+        <target state="new">Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="NullValidatorTypeTitle">
+        <source>Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</source>
+        <target state="new">Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingEnumerableValidationTitle">
+        <source>Member potentially missing enumerable validation.</source>
+        <target state="new">Member potentially missing enumerable validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationMessage">
+        <source>Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</source>
+        <target state="new">Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="PotentiallyMissingTransitiveValidationTitle">
+        <source>Member potentially missing transitive validation.</source>
+        <target state="new">Member potentially missing transitive validation.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
+        <source>Validator type {0} doesn't have a parameterless constructor.</source>
+        <target state="new">Validator type {0} doesn't have a parameterless constructor.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="ValidatorsNeedSimpleConstructorTitle">
+        <source>Validators used for transitive or enumerable validation must have a constructor with no parameters.</source>
+        <target state="new">Validators used for transitive or enumerable validation must have a constructor with no parameters.</target>
+        <note />
+      </trans-unit>
+    </body>
+  </file>
+</xliff>
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/SymbolHolder.cs b/src/libraries/Microsoft.Extensions.Options/gen/SymbolHolder.cs
new file mode 100644 (file)
index 0000000..c78106e
--- /dev/null
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.Extensions.Options.Generators
+{
+    /// <summary>
+    /// Holds required symbols for the <see cref="Generator"/>.
+    /// </summary>
+    internal sealed record class SymbolHolder(
+        INamedTypeSymbol OptionsValidatorSymbol,
+        INamedTypeSymbol ValidationAttributeSymbol,
+        INamedTypeSymbol DataTypeAttributeSymbol,
+        INamedTypeSymbol ValidateOptionsSymbol,
+        INamedTypeSymbol IValidatableObjectSymbol,
+        INamedTypeSymbol TypeSymbol,
+        INamedTypeSymbol? ValidateObjectMembersAttributeSymbol,
+        INamedTypeSymbol? ValidateEnumeratedItemsAttributeSymbol);
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/SymbolLoader.cs b/src/libraries/Microsoft.Extensions.Options/gen/SymbolLoader.cs
new file mode 100644 (file)
index 0000000..6f805e9
--- /dev/null
@@ -0,0 +1,68 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.Extensions.Options.Generators
+{
+    internal static class SymbolLoader
+    {
+        public const string OptionsValidatorAttribute = "Microsoft.Extensions.Options.OptionsValidatorAttribute";
+        internal const string ValidationAttribute = "System.ComponentModel.DataAnnotations.ValidationAttribute";
+        internal const string DataTypeAttribute = "System.ComponentModel.DataAnnotations.DataTypeAttribute";
+        internal const string IValidatableObjectType = "System.ComponentModel.DataAnnotations.IValidatableObject";
+        internal const string IValidateOptionsType = "Microsoft.Extensions.Options.IValidateOptions`1";
+        internal const string TypeOfType = "System.Type";
+        internal const string ValidateObjectMembersAttribute = "Microsoft.Extensions.Options.ValidateObjectMembersAttribute";
+        internal const string ValidateEnumeratedItemsAttribute = "Microsoft.Extensions.Options.ValidateEnumeratedItemsAttribute";
+
+        public static bool TryLoad(Compilation compilation, out SymbolHolder? symbolHolder)
+        {
+            INamedTypeSymbol? GetSymbol(string metadataName, bool optional = false)
+            {
+                var symbol = compilation.GetTypeByMetadataName(metadataName);
+                if (symbol == null && !optional)
+                {
+                    return null;
+                }
+
+                return symbol;
+            }
+
+            // required
+            var optionsValidatorSymbol = GetSymbol(OptionsValidatorAttribute);
+            var validationAttributeSymbol = GetSymbol(ValidationAttribute);
+            var dataTypeAttributeSymbol = GetSymbol(DataTypeAttribute);
+            var ivalidatableObjectSymbol = GetSymbol(IValidatableObjectType);
+            var validateOptionsSymbol = GetSymbol(IValidateOptionsType);
+            var typeSymbol = GetSymbol(TypeOfType);
+
+    #pragma warning disable S1067 // Expressions should not be too complex
+            if (optionsValidatorSymbol == null ||
+                validationAttributeSymbol == null ||
+                dataTypeAttributeSymbol == null ||
+                ivalidatableObjectSymbol == null ||
+                validateOptionsSymbol == null ||
+                typeSymbol == null)
+            {
+                symbolHolder = default;
+                return false;
+            }
+    #pragma warning restore S1067 // Expressions should not be too complex
+
+            symbolHolder = new(
+                optionsValidatorSymbol,
+                validationAttributeSymbol,
+                dataTypeAttributeSymbol,
+                validateOptionsSymbol,
+                ivalidatableObjectSymbol,
+                typeSymbol,
+
+                // optional
+                GetSymbol(ValidateObjectMembersAttribute, optional: true),
+                GetSymbol(ValidateEnumeratedItemsAttribute, optional: true));
+
+            return true;
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/TypeDeclarationSyntaxReceiver.cs b/src/libraries/Microsoft.Extensions.Options/gen/TypeDeclarationSyntaxReceiver.cs
new file mode 100644 (file)
index 0000000..f007c53
--- /dev/null
@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Microsoft.Extensions.Options.Generators
+{
+    /// <summary>
+    /// Class/struct/record declaration syntax receiver for generators.
+    /// </summary>
+    internal sealed class TypeDeclarationSyntaxReceiver : ISyntaxReceiver
+    {
+        internal static ISyntaxReceiver Create() => new TypeDeclarationSyntaxReceiver();
+
+        /// <summary>
+        /// Gets class/struct/record declaration syntax holders after visiting nodes.
+        /// </summary>
+        public ICollection<TypeDeclarationSyntax> TypeDeclarations { get; } = new List<TypeDeclarationSyntax>();
+
+        /// <inheritdoc/>
+        public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
+        {
+            if (syntaxNode is ClassDeclarationSyntax classSyntax)
+            {
+                TypeDeclarations.Add(classSyntax);
+            }
+            else if (syntaxNode is StructDeclarationSyntax structSyntax)
+            {
+                TypeDeclarations.Add(structSyntax);
+            }
+            else if (syntaxNode is RecordDeclarationSyntax recordSyntax)
+            {
+                TypeDeclarations.Add(recordSyntax);
+            }
+            else if (syntaxNode is InterfaceDeclarationSyntax interfaceSyntax)
+            {
+                TypeDeclarations.Add(interfaceSyntax);
+            }
+        }
+    }
+}
index 6ae6095..d810360 100644 (file)
@@ -215,6 +215,10 @@ namespace Microsoft.Extensions.Options
         public string OptionsName { get { throw null; } }
         public System.Type OptionsType { get { throw null; } }
     }
+    [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct)]
+    public sealed class OptionsValidatorAttribute : System.Attribute
+    {
+    }
     public partial class OptionsWrapper<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TOptions> : Microsoft.Extensions.Options.IOptions<TOptions> where TOptions : class
     {
         public OptionsWrapper(TOptions options) { }
@@ -282,6 +286,20 @@ namespace Microsoft.Extensions.Options
         public virtual void PostConfigure(string? name, TOptions options) { }
         public void PostConfigure(TOptions options) { }
     }
+    [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field)]
+    public sealed class ValidateEnumeratedItemsAttribute : System.Attribute
+    {
+        public ValidateEnumeratedItemsAttribute() {}
+        public ValidateEnumeratedItemsAttribute(System.Type validator) { throw null; }
+        public System.Type? Validator { get {throw null; } }
+    }
+    [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field)]
+    public sealed class ValidateObjectMembersAttribute : System.Attribute
+    {
+        public ValidateObjectMembersAttribute() {}
+        public ValidateObjectMembersAttribute(System.Type validator) { throw null; }
+        public System.Type? Validator { get { throw null; } }
+    }
     public partial class ValidateOptionsResult
     {
         public static readonly Microsoft.Extensions.Options.ValidateOptionsResult Skip;
index 202e943..adfda8a 100644 (file)
     <Reference Include="System.ComponentModel.DataAnnotations" />
   </ItemGroup>
 
+  <ItemGroup>
+    <ProjectReference Include="..\gen\Microsoft.Extensions.Options.SourceGeneration.csproj"
+                      ReferenceOutputAssembly="false"
+                      PackAsAnalyzer="true" />
+  </ItemGroup>
+
 </Project>
diff --git a/src/libraries/Microsoft.Extensions.Options/src/OptionsValidatorAttribute.cs b/src/libraries/Microsoft.Extensions.Options/src/OptionsValidatorAttribute.cs
new file mode 100644 (file)
index 0000000..39e6aa3
--- /dev/null
@@ -0,0 +1,16 @@
+// 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.Diagnostics;
+
+namespace Microsoft.Extensions.Options
+{
+    /// <summary>
+    /// Triggers the automatic generation of the implementation of <see cref="Microsoft.Extensions.Options.IValidateOptions{T}" /> at compile time.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
+    public sealed class OptionsValidatorAttribute : Attribute
+    {
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/src/ValidateEnumeratedItemsAttribute.cs b/src/libraries/Microsoft.Extensions.Options/src/ValidateEnumeratedItemsAttribute.cs
new file mode 100644 (file)
index 0000000..7f36302
--- /dev/null
@@ -0,0 +1,45 @@
+// 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.Diagnostics.CodeAnalysis;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Extensions.Options
+{
+    /// <summary>
+    /// Marks a field or property to be enumerated, and each enumerated object to be validated.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
+    public sealed class ValidateEnumeratedItemsAttribute : Attribute
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ValidateEnumeratedItemsAttribute"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Using this constructor for a field/property tells the code generator to
+        /// generate validation for the individual members of the enumerable's type.
+        /// </remarks>
+        public ValidateEnumeratedItemsAttribute()
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ValidateEnumeratedItemsAttribute"/> class.
+        /// </summary>
+        /// <param name="validator">A type that implements <see cref="IValidateOptions{T}" /> for the enumerable's type.</param>
+        /// <remarks>
+        /// Using this constructor for a field/property tells the code generator to use the given type to validate
+        /// the object held by the enumerable.
+        /// </remarks>
+        public ValidateEnumeratedItemsAttribute(Type validator)
+        {
+            Validator = validator;
+        }
+
+        /// <summary>
+        /// Gets the type to use to validate the enumerable's objects.
+        /// </summary>
+        public Type? Validator { get; }
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/src/ValidateObjectMembersAttribute.cs b/src/libraries/Microsoft.Extensions.Options/src/ValidateObjectMembersAttribute.cs
new file mode 100644 (file)
index 0000000..69a36ca
--- /dev/null
@@ -0,0 +1,44 @@
+// 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 Microsoft.Extensions.Options;
+
+namespace Microsoft.Extensions.Options
+{
+    /// <summary>
+    /// Marks a field or property to be validated transitively.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
+    public sealed class ValidateObjectMembersAttribute : Attribute
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ValidateObjectMembersAttribute"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Using this constructor for a field/property tells the code generator to
+        /// generate validation for the individual members of the field/property's type.
+        /// </remarks>
+        public ValidateObjectMembersAttribute()
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ValidateObjectMembersAttribute"/> class.
+        /// </summary>
+        /// <param name="validator">A type that implements <see cref="IValidateOptions{T}" /> for the field/property's type.</param>
+        /// <remarks>
+        /// Using this constructor for a field/property tells the code generator to use the given type to validate
+        /// the object held by the field/property.
+        /// </remarks>
+        public ValidateObjectMembersAttribute(Type validator)
+        {
+            Validator = validator;
+        }
+
+        /// <summary>
+        /// Gets the type to use to validate a field or property.
+        /// </summary>
+        public Type? Validator { get; }
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs
new file mode 100644 (file)
index 0000000..ad094f9
--- /dev/null
@@ -0,0 +1,1246 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Options.Generators;
+using SourceGenerators.Tests;
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.ComponentModel.DataAnnotations;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.Gen.OptionsValidation.Unit.Test;
+
+public class EmitterTests
+{
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task TestEmitterWithCustomValidator()
+    {
+        string source = """
+            using System;
+            using System.ComponentModel.DataAnnotations;
+            using Microsoft.Extensions.Options;
+
+            #nullable enable
+
+            namespace HelloWorld
+            {
+                public class MyOptions
+                {
+                    [Required]
+                    public string Val1 { get; set; } = string.Empty;
+
+                    [Range(1, 3)]
+                    public int Val2 { get; set; }
+                }
+
+                [OptionsValidator]
+                public partial struct MyOptionsValidator : IValidateOptions<MyOptions>
+                {
+                }
+            }
+            """;
+
+        string generatedSource = """
+
+    // <auto-generated/>
+    #nullable enable
+    #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
+    namespace HelloWorld
+{
+    partial struct MyOptionsValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::HelloWorld.MyOptions options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "MyOptions" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val1";
+            context.DisplayName = baseName + "Val1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "Val2";
+            context.DisplayName = baseName + "Val2";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val2!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal static class __Attributes
+    {
+        internal static readonly global::System.ComponentModel.DataAnnotations.RequiredAttribute A1 = new global::System.ComponentModel.DataAnnotations.RequiredAttribute();
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A2 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+            (int)1,
+            (int)3);
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal static class __Validators
+    {
+    }
+}
+
+""";
+
+        var (diagnostics, generatedSources) = await RoslynTestUtils.RunGenerator(
+            new Generator(),
+            new[]
+            {
+                Assembly.GetAssembly(typeof(RequiredAttribute))!,
+                Assembly.GetAssembly(typeof(OptionsValidatorAttribute))!,
+                Assembly.GetAssembly(typeof(IValidateOptions<object>))!,
+            },
+            new List<string> { source })
+            .ConfigureAwait(false);
+
+        Assert.Empty(diagnostics);
+        _ = Assert.Single(generatedSources);
+
+        Assert.Equal(generatedSource.Replace("\r\n", "\n"), generatedSources[0].SourceText.ToString().Replace("\r\n", "\n"));
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task PotentiallyMissingAttributes()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            public class FirstModel
+            {
+                [Required]
+                public SecondModel? P1 { get; set; }
+
+                [Required]
+                public System.Collections.Generic.IList<SecondModel>? P2 { get; set; }
+            }
+
+            public class SecondModel
+            {
+                [Required]
+                public string? P3;
+            }
+
+            [OptionsValidator]
+            public partial class FirstValidator : IValidateOptions<FirstModel>
+            {
+            }
+        ");
+
+        Assert.Equal(2, diagnostics.Count);
+        Assert.Equal(DiagDescriptors.PotentiallyMissingTransitiveValidation.Id, diagnostics[0].Id);
+        Assert.Equal(DiagDescriptors.PotentiallyMissingEnumerableValidation.Id, diagnostics[1].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task CircularTypeReferences()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            public class FirstModel
+            {
+                [Required]
+                [ValidateObjectMembers]
+                public FirstModel? P1 { get; set; }
+            }
+
+            [OptionsValidator]
+            public partial class FirstValidator : IValidateOptions<FirstModel>
+            {
+            }
+        ");
+
+        _ = Assert.Single(diagnostics);
+        Assert.Equal(DiagDescriptors.CircularTypeReferences.Id, diagnostics[0].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task InvalidValidatorInterface()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            public class FirstModel
+            {
+                [Required]
+                public string? P1;
+            }
+
+            public class SecondModel
+            {
+                [Required]
+                public string? P2;
+            }
+
+            [OptionsValidator]
+            public partial class FirstValidator : IValidateOptions<FirstModel>
+            {
+            }
+
+            [OptionsValidator]
+            public partial class SecondValidator
+            {
+            }
+        ");
+
+        _ = Assert.Single(diagnostics);
+        Assert.Equal(DiagDescriptors.DoesntImplementIValidateOptions.Id, diagnostics[0].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task NotValidator()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            public class FirstModel
+            {
+                [ValidateObjectMembers(typeof(SecondValidator)]
+                public SecondModel? P1;
+            }
+
+            public class SecondModel
+            {
+                [Required]
+                public string? P2;
+            }
+
+            [OptionsValidator]
+            public partial class FirstValidator : IValidateOptions<FirstModel>
+            {
+            }
+
+            public partial class SecondValidator
+            {
+            }
+        ");
+
+        _ = Assert.Single(diagnostics);
+        Assert.Equal(DiagDescriptors.DoesntImplementIValidateOptions.Id, diagnostics[0].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task ValidatorAlreadyImplementValidateFunction()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            public class FirstModel
+            {
+                [Required]
+                public string? P1;
+
+                [ValidateObjectMembers(typeof(SecondValidator)]
+                public SecondModel? P2;
+            }
+
+            public class SecondModel
+            {
+                [Required]
+                public string? P3;
+            }
+
+            [OptionsValidator]
+            public partial class FirstValidator : IValidateOptions<FirstModel>
+            {
+            }
+
+            [OptionsValidator]
+            public partial class SecondValidator : IValidateOptions<SecondModel>
+            {
+                public ValidateOptionsResult Validate(string name, SecondModel options)
+                {
+                    throw new System.NotSupportedException();
+                }
+            }
+        ");
+
+        _ = Assert.Single(diagnostics);
+        Assert.Equal(DiagDescriptors.AlreadyImplementsValidateMethod.Id, diagnostics[0].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task NullValidator()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            public class FirstModel
+            {
+                [ValidateObjectMembers(null!)]
+                public SecondModel? P1;
+            }
+
+            public class SecondModel
+            {
+                [Required]
+                public string? P2;
+            }
+
+            [OptionsValidator]
+            public partial class FirstValidator : IValidateOptions<FirstModel>
+            {
+            }
+
+            [OptionsValidator]
+            public partial class SecondValidator : IValidateOptions<SecondModel>
+            {
+            }
+        ");
+
+        _ = Assert.Single(diagnostics);
+        Assert.Equal(DiagDescriptors.NullValidatorType.Id, diagnostics[0].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task NoSimpleValidatorConstructor()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            public class FirstModel
+            {
+                [Required]
+                public string? P1;
+
+                [ValidateObjectMembers(typeof(SecondValidator)]
+                public SecondModel? P2;
+            }
+
+            public class SecondModel
+            {
+                [Required]
+                public string? P3;
+            }
+
+            [OptionsValidator]
+            public partial class FirstValidator : IValidateOptions<FirstModel>
+            {
+            }
+
+            [OptionsValidator]
+            public partial class SecondValidator : IValidateOptions<SecondModel>
+            {
+                public SecondValidator(int _)
+                {
+                }
+            }
+        ");
+
+        _ = Assert.Single(diagnostics);
+        Assert.Equal(DiagDescriptors.ValidatorsNeedSimpleConstructor.Id, diagnostics[0].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task NoStaticValidator()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            public class FirstModel
+            {
+                [Required]
+                public string P1;
+            }
+
+            [OptionsValidator]
+            public static partial class FirstValidator : IValidateOptions<FirstModel>
+            {
+            }
+        ");
+
+        _ = Assert.Single(diagnostics);
+        Assert.Equal(DiagDescriptors.CantBeStaticClass.Id, diagnostics[0].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task BogusModelType()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            [OptionsValidator]
+            public partial class FirstValidator : IValidateOptions<Bogus>
+            {
+            }
+        ");
+
+        // the generator doesn't produce any errors here, since the C# compiler will take care of it
+        Assert.Empty(diagnostics);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task CantValidateOpenGenericMembers()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            public class FirstModel<T>
+            {
+                [Required]
+                [ValidateObjectMembers]
+                public T? P1;
+
+                [ValidateObjectMembers]
+                [Required]
+                public T[]? P2;
+
+                [ValidateObjectMembers]
+                [Required]
+                public System.Collections.Generics.IList<T> P3 = null!;
+            }
+
+            [OptionsValidator]
+            public partial class FirstValidator<T> : IValidateOptions<FirstModel<T>>
+            {
+            }
+        ");
+
+        Assert.Equal(3, diagnostics.Count);
+        Assert.Equal(DiagDescriptors.CantUseWithGenericTypes.Id, diagnostics[0].Id);
+        Assert.Equal(DiagDescriptors.CantUseWithGenericTypes.Id, diagnostics[1].Id);
+        Assert.Equal(DiagDescriptors.CantUseWithGenericTypes.Id, diagnostics[2].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task ClosedGenerics()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            public class FirstModel<T>
+            {
+                [Required]
+                [ValidateObjectMembers]
+                public T? P1;
+
+                [ValidateObjectMembers]
+                [Required]
+                public T[]? P2;
+
+                [ValidateObjectMembers]
+                [Required]
+                public int[]? P3;
+
+                [ValidateObjectMembers]
+                [Required]
+                public System.Collections.Generics.IList<T>? P4;
+            }
+
+            [OptionsValidator]
+            public partial class FirstValidator : IValidateOptions<FirstModel<string>>
+            {
+            }
+        ");
+
+        Assert.Equal(4, diagnostics.Count);
+        Assert.Equal(DiagDescriptors.NoEligibleMember.Id, diagnostics[0].Id);
+        Assert.Equal(DiagDescriptors.NoEligibleMember.Id, diagnostics[1].Id);
+        Assert.Equal(DiagDescriptors.NoEligibleMember.Id, diagnostics[2].Id);
+        Assert.Equal(DiagDescriptors.NoEligibleMember.Id, diagnostics[3].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task NoEligibleMembers()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            public class FirstModel
+            {
+                [Required]
+                [ValidateObjectMembers]
+                public SecondModel? P1;
+            }
+
+            public class SecondModel
+            {
+                public string P2;
+            }
+
+            [OptionsValidator]
+            public partial class FirstValidator : IValidateOptions<FirstModel>
+            {
+            }
+
+            [OptionsValidator]
+            public partial class SecondValidator : IValidateOptions<SecondModel>
+            {
+            }
+        ");
+
+        Assert.Equal(2, diagnostics.Count);
+        Assert.Equal(DiagDescriptors.NoEligibleMember.Id, diagnostics[0].Id);
+        Assert.Equal(DiagDescriptors.NoEligibleMembersFromValidator.Id, diagnostics[1].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task AlreadyImplemented()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            public class FirstModel
+            {
+                [Required]
+                public string One { get; set; } = string.Empty;
+            }
+
+            [OptionsValidator]
+            public partial class FirstValidator : IValidateOptions<FirstModel>
+            {
+                public void Validate(string name, FirstModel fm)
+                {
+                }
+            }
+        ");
+
+        _ = Assert.Single(diagnostics);
+        Assert.Equal(DiagDescriptors.AlreadyImplementsValidateMethod.Id, diagnostics[0].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task ShouldNotProduceInfoWhenTheClassHasABaseClass()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+                public class Parent
+                {
+                    [Required]
+                    public string parentString { get; set; }
+                }
+
+                public class Child : Parent
+                {
+                    [Required]
+                    public string childString { get; set; }
+                }
+
+                [OptionsValidator]
+                public partial class Validator : IValidateOptions<Child>
+                {
+                }
+            ");
+
+        Assert.Empty(diagnostics);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task ShouldNotProduceInfoWhenTransitiveClassHasABaseClass()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+                public class Parent
+                {
+                    [Required]
+                    public string parentString { get; set; }
+                }
+
+                public class Child : Parent
+                {
+                    [Required]
+                    public string childString { get; set; }
+                }
+
+                public class MyOptions
+                {
+                    [ValidateObjectMembers]
+                    public Child childVal { get; set; }
+                }
+
+                [OptionsValidator]
+                public partial class Validator : IValidateOptions<MyOptions>
+                {
+                }
+            ");
+
+        Assert.Empty(diagnostics);
+    }
+
+    [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    [InlineData("bool")]
+    [InlineData("int")]
+    [InlineData("double")]
+    [InlineData("string")]
+    [InlineData("System.String")]
+    [InlineData("System.DateTime")]
+    public async Task ShouldProduceWarn_WhenTransitiveAttrMisused(string memberClass)
+    {
+        var (diagnostics, _) = await RunGenerator(@$"
+                public class InnerModel
+                {{
+                    [Required]
+                    public string childString {{ get; set; }}
+                }}
+
+                public class MyOptions
+                {{
+                    [Required]
+                    public string simpleVal {{ get; set; }}
+
+                    [ValidateObjectMembers]
+                    public {memberClass} complexVal {{ get; set; }}
+                }}
+
+                [OptionsValidator]
+                public partial class Validator : IValidateOptions<MyOptions>
+                {{
+                }}
+            ");
+
+        Assert.Single(diagnostics);
+        Assert.Equal(DiagDescriptors.NoEligibleMember.Id, diagnostics[0].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task ShouldProduceWarningWhenTheClassHasNoEligibleMembers()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+                public class Child
+                {
+                    private string AccountName { get; set; }
+                    public object Weight;
+                }
+
+                [OptionsValidator]
+                public partial class Validator : IValidateOptions<Child>
+                {
+                }
+            ");
+
+        Assert.Single(diagnostics);
+        Assert.Equal(DiagDescriptors.NoEligibleMembersFromValidator.Id, diagnostics[0].Id);
+    }
+
+    [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    [InlineData("private")]
+    [InlineData("protected")]
+    public async Task ShouldProduceWarningWhenTheClassMembersAreInaccessible(string accessModifier)
+    {
+        var (diagnostics, _) = await RunGenerator($@"
+                public class Model
+                {{
+                    [Required]
+                    public string? PublicVal {{ get; set; }}
+
+                    [Required]
+                    {accessModifier} string? Val {{ get; set; }}
+                }}
+
+                [OptionsValidator]
+                public partial class Validator : IValidateOptions<Model>
+                {{
+                }}
+            ");
+
+        Assert.Single(diagnostics);
+        Assert.Equal("SYSLIB1206", diagnostics[0].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task ShouldNotProduceErrorWhenMultipleValidationAnnotationsExist()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+                public class IValidateOptionsTestFile
+                {
+                    [MinLength(5)]
+                    [MaxLength(15)]
+                    public string Val9 { get; set; }
+                }
+
+                [OptionsValidator]
+                public partial class Validator : IValidateOptions<IValidateOptionsTestFile>
+                {
+                }
+            ");
+
+        Assert.Empty(diagnostics);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task ShouldNotProduceErrorWhenDataTypeAttributesAreUsed()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+                public class IValidateOptionsTestFile
+                {
+                    [CreditCard]
+                    public string Val3 = """";
+
+                    [EmailAddress]
+                    public string Val6 { get; set; }
+
+                    [EnumDataType(typeof(string))]
+                    public string Val7 { get; set; }
+
+                    [FileExtensions]
+                    public string Val8 { get; set; }
+
+                    [Phone]
+                    public string Val10 { get; set; }
+
+                    [Url]
+                    public string Val11 { get; set; }
+                }
+
+                [OptionsValidator]
+                public partial class Validator : IValidateOptions<IValidateOptionsTestFile>
+                {
+                }
+            ");
+
+        Assert.Empty(diagnostics);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task ShouldNotProduceErrorWhenConstVariableIsUsedAsAttributeArgument()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+                public class IValidateOptionsTestFile
+                {
+                    private const int q = 5;
+                    [Range(q, 10)]
+                    public string Val11 { get; set; }
+                }
+
+                [OptionsValidator]
+                public partial class Validator : IValidateOptions<IValidateOptionsTestFile>
+                {
+                }
+            ");
+
+        Assert.Empty(diagnostics);
+    }
+
+    // Testing on all existing & eligible annotations extending ValidationAttribute that aren't used above
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task ShouldNotProduceAnyMessagesWhenExistingValidationsArePlaced()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+                public class IValidateOptionsTestFile
+                {
+                    [Required]
+                    public string Val { get; set; }
+
+                    [Compare(""val"")]
+                    public string Val2 { get; set; }
+
+                    [DataType(DataType.Password)]
+                    public string _val5 = """";
+
+                    [Range(5.1, 10.11)]
+                    public string Val12 { get; set; }
+
+                    [Range(typeof(MemberDeclarationSyntax), ""1/2/2004"", ""3/4/2004"")]
+                    public string Val14 { get; set; }
+
+                    [RegularExpression("""")]
+                    public string Val15 { get; set; }
+
+                    [StringLength(5)]
+                    public string Val16 { get; set; }
+
+                    [CustomValidation(typeof(MemberDeclarationSyntax), ""CustomMethod"")]
+                    public string Val17 { get; set; }
+                }
+
+                [OptionsValidator]
+                public partial class Validator : IValidateOptions<IValidateOptionsTestFile>
+                {
+                }
+            ");
+
+        Assert.Empty(diagnostics);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task ShouldNotProduceErrorWhenPropertiesAreUsedAsAttributeArgument()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+                public class IValidateOptionsTestFile
+                {
+                    private const int q = 5;
+                    [Range(q, 10, ErrorMessage = ""ErrorMessage"")]
+                    public string Val11 { get; set; }
+                }
+
+                [OptionsValidator]
+                public partial class Validator : IValidateOptions<IValidateOptionsTestFile>
+                {
+                }
+            ");
+
+        Assert.Empty(diagnostics);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task ShouldSkipWhenOptionsValidatorAttributeDoesNotExist()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+                public class IValidateOptionsTestFile
+                {
+                    private const int q = 5;
+                    [Range(q, 10, ErrorMessage = ""ErrorMessage"")]
+                    public string Val11 { get; set; }
+                }
+
+                [OptionsValidator]
+                public partial class Validator : IValidateOptions<IValidateOptionsTestFile>
+                {
+                }
+            ", includeOptionValidatorReferences: false);
+
+        Assert.Empty(diagnostics);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task ShouldSkipAtrributeWhenAttributeSymbolCannotBeFound()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+                public class IValidateOptionsTestFile
+                {
+                    [RandomTest]
+                    public string Val11 { get; set; }
+
+                    [Range(1, 10, ErrorMessage = ""ErrorMessage"")]
+                    public string Val12 { get; set; }
+                }
+
+                [OptionsValidator]
+                public partial class Validator : IValidateOptions<IValidateOptionsTestFile>
+                {
+                }
+            ");
+
+        Assert.Empty(diagnostics);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task ShouldSkipAtrributeWhenAttributeSymbolIsNotBasedOnValidationAttribute()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+                public class IValidateOptionsTestFile
+                {
+                    [FilterUIHint(""MultiForeignKey"")]
+                    public string Val11 { get; set; }
+
+                    [Range(1, 10, ErrorMessage = ""ErrorMessage"")]
+                    public string Val12 { get; set; }
+                }
+
+                [OptionsValidator]
+                public partial class Validator : IValidateOptions<IValidateOptionsTestFile>
+                {
+                }
+            ");
+
+        Assert.Empty(diagnostics);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task ShouldAcceptAtrributeWhenAttributeIsInDifferentNamespace()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+                namespace Test {
+                    public class IValidateOptionsTestFile
+                    {
+                        [Test]
+                        public string Val11 { get; set; }
+                    }
+
+                    [AttributeUsage(AttributeTargets.Class)]
+                    public sealed class TestAttribute : ValidationAttribute
+                    {
+                    }
+
+                    [OptionsValidator]
+                    public partial class Validator : IValidateOptions<IValidateOptionsTestFile>
+                    {
+                    }
+                }
+            ", inNamespace: false);
+
+        Assert.Empty(diagnostics);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task ShouldHandleAtrributePropertiesOtherThanString()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+                namespace Test {
+                    public class IValidateOptionsTestFile
+                    {
+                        [Test(num = 5)]
+                        public string Val11 { get; set; }
+
+                        [Required]
+                        public string Val12 { get; set; }
+                    }
+
+                    [OptionsValidator]
+                    public partial class Validator : IValidateOptions<IValidateOptionsTestFile>
+                    {
+                    }
+                }
+
+                namespace System.ComponentModel.DataAnnotations {
+                    [AttributeUsage(AttributeTargets.Class)]
+                    public sealed class TestAttribute : ValidationAttribute
+                    {
+                        public int num { get; set; }
+                        public TestAttribute() {
+                        }
+                    }
+                }
+            ", inNamespace: false);
+
+        Assert.Empty(diagnostics);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task ShouldStoreFloatValuesCorrectly()
+    {
+        var backupCulture = CultureInfo.CurrentCulture;
+        CultureInfo.CurrentCulture = new CultureInfo("ru-RU", false);
+        try
+        {
+            var (diagMessages, generatedResults) = await RunGenerator(@"
+                    public class Model
+                    {
+                        [Range(-0.1, 1.3)]
+                        public string Val { get; set; }
+                    }
+
+                    [OptionsValidator]
+                    public partial class Validator : IValidateOptions<Model>
+                    {
+                    }
+                ");
+
+            Assert.Empty(diagMessages);
+            Assert.Single(generatedResults);
+            Assert.DoesNotContain("0,1", generatedResults[0].SourceText.ToString());
+            Assert.DoesNotContain("1,3", generatedResults[0].SourceText.ToString());
+        }
+        finally
+        {
+            CultureInfo.CurrentCulture = backupCulture;
+        }
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task MultiModelValidatorGeneratesOnlyOnePartialTypeBlock()
+    {
+        var (diagnostics, sources) = await RunGenerator(@"
+            public class FirstModel
+            {
+                [Required]
+                public string P1 { get; set; }
+            }
+
+            public class SecondModel
+            {
+                [Required]
+                public string P2 { get; set; }
+            }
+
+            public class ThirdModel
+            {
+                [Required]
+                public string P3 { get; set; }
+            }
+
+            [OptionsValidator]
+            public partial class MultiValidator : IValidateOptions<FirstModel>, IValidateOptions<SecondModel>, IValidateOptions<ThirdModel>
+            {
+            }
+        ");
+
+        var typeDeclarations = sources[0].SyntaxTree
+            .GetRoot()
+            .DescendantNodes()
+            .OfType<TypeDeclarationSyntax>()
+            .ToArray();
+
+        var multiValidatorTypeDeclarations = typeDeclarations
+            .Where(x => x.Identifier.ValueText == "MultiValidator")
+            .ToArray();
+
+        Assert.Single(multiValidatorTypeDeclarations);
+
+        var validateMethodDeclarations = multiValidatorTypeDeclarations[0]
+            .DescendantNodes()
+            .OfType<MethodDeclarationSyntax>()
+            .Where(x => x.Identifier.ValueText == "Validate")
+            .ToArray();
+
+        Assert.Equal(3, validateMethodDeclarations.Length);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task CircularTypeReferencesInEnumeration()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            public class FirstModel
+            {
+                [Required]
+                [ValidateEnumeratedItems]
+                public FirstModel[]? P1 { get; set; }
+            }
+
+            [OptionsValidator]
+            public partial class FirstValidator : IValidateOptions<FirstModel>
+            {
+            }
+        ");
+
+        _ = Assert.Single(diagnostics);
+        Assert.Equal(DiagDescriptors.CircularTypeReferences.Id, diagnostics[0].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task NotValidatorInEnumeration()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            public class FirstModel
+            {
+                [ValidateEnumeratedItems(typeof(SecondValidator)]
+                public SecondModel[]? P1;
+            }
+
+            public class SecondModel
+            {
+                [Required]
+                public string? P2;
+            }
+
+            [OptionsValidator]
+            public partial class FirstValidator : IValidateOptions<FirstModel>
+            {
+            }
+
+            public partial class SecondValidator
+            {
+            }
+        ");
+
+        _ = Assert.Single(diagnostics);
+        Assert.Equal(DiagDescriptors.DoesntImplementIValidateOptions.Id, diagnostics[0].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task NullValidatorInEnumeration()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            public class FirstModel
+            {
+                [ValidateEnumeratedItems(null!)]
+                public SecondModel[]? P1;
+            }
+
+            public class SecondModel
+            {
+                [Required]
+                public string? P2;
+            }
+
+            [OptionsValidator]
+            public partial class FirstValidator : IValidateOptions<FirstModel>
+            {
+            }
+
+            [OptionsValidator]
+            public partial class SecondValidator : IValidateOptions<SecondModel>
+            {
+            }
+        ");
+
+        _ = Assert.Single(diagnostics);
+        Assert.Equal(DiagDescriptors.NullValidatorType.Id, diagnostics[0].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task NoSimpleValidatorConstructorInEnumeration()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            public class FirstModel
+            {
+                [Required]
+                public string? P1;
+
+                [ValidateEnumeratedItems(typeof(SecondValidator)]
+                public SecondModel[]? P2;
+            }
+
+            public class SecondModel
+            {
+                [Required]
+                public string? P3;
+            }
+
+            [OptionsValidator]
+            public partial class FirstValidator : IValidateOptions<FirstModel>
+            {
+            }
+
+            [OptionsValidator]
+            public partial class SecondValidator : IValidateOptions<SecondModel>
+            {
+                public SecondValidator(int _)
+                {
+                }
+            }
+        ");
+
+        _ = Assert.Single(diagnostics);
+        Assert.Equal(DiagDescriptors.ValidatorsNeedSimpleConstructor.Id, diagnostics[0].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task CantValidateOpenGenericMembersInEnumeration()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            public class FirstModel<T>
+            {
+                [Required]
+                [ValidateEnumeratedItems]
+                public T[]? P1;
+
+                [ValidateEnumeratedItems]
+                [Required]
+                public T[]? P2;
+
+                [ValidateEnumeratedItems]
+                [Required]
+                public System.Collections.Generic.IList<T> P3 = null!;
+            }
+
+            [OptionsValidator]
+            public partial class FirstValidator<T> : IValidateOptions<FirstModel<T>>
+            {
+            }
+        ");
+
+        Assert.Equal(3, diagnostics.Count);
+        Assert.Equal(DiagDescriptors.CantUseWithGenericTypes.Id, diagnostics[0].Id);
+        Assert.Equal(DiagDescriptors.CantUseWithGenericTypes.Id, diagnostics[1].Id);
+        Assert.Equal(DiagDescriptors.CantUseWithGenericTypes.Id, diagnostics[2].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task ClosedGenericsInEnumeration()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            public class FirstModel<T>
+            {
+                [ValidateEnumeratedItems]
+                [Required]
+                public T[]? P1;
+
+                [ValidateEnumeratedItems]
+                [Required]
+                public int[]? P2;
+
+                [ValidateEnumeratedItems]
+                [Required]
+                public System.Collections.Generic.IList<T>? P3;
+            }
+
+            [OptionsValidator]
+            public partial class FirstValidator : IValidateOptions<FirstModel<string>>
+            {
+            }
+        ");
+
+        Assert.Equal(3, diagnostics.Count);
+        Assert.Equal(DiagDescriptors.NoEligibleMember.Id, diagnostics[0].Id);
+        Assert.Equal(DiagDescriptors.NoEligibleMember.Id, diagnostics[1].Id);
+        Assert.Equal(DiagDescriptors.NoEligibleMember.Id, diagnostics[2].Id);
+    }
+
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task NotEnumerable()
+    {
+        var (diagnostics, _) = await RunGenerator(@"
+            public class FirstModel
+            {
+                [Required]
+                [ValidateEnumeratedItems]
+                public int P1;
+            }
+
+            [OptionsValidator]
+            public partial class FirstValidator : IValidateOptions<FirstModel>
+            {
+            }
+        ");
+
+        Assert.Equal(1, diagnostics.Count);
+        Assert.Equal(DiagDescriptors.NotEnumerableType.Id, diagnostics[0].Id);
+    }
+
+    private static async Task<(IReadOnlyList<Diagnostic> diagnostics, ImmutableArray<GeneratedSourceResult> generatedSources)> RunGenerator(
+        string code,
+        bool wrap = true,
+        bool inNamespace = true,
+        bool includeOptionValidatorReferences = true,
+        bool includeSystemReferences = true,
+        bool includeOptionsReferences = true,
+        bool includeTransitiveReferences = true)
+    {
+        var text = code;
+        if (wrap)
+        {
+            var nspaceStart = "namespace Test {";
+            var nspaceEnd = "}";
+            if (!inNamespace)
+            {
+                nspaceStart = "";
+                nspaceEnd = "";
+            }
+
+            text = $@"
+                    {nspaceStart}
+                    using System.ComponentModel.DataAnnotations;
+                    using Microsoft.Extensions.Options.Validation;
+                    using Microsoft.Shared.Data.Validation;
+                    using Microsoft.Extensions.Options;
+                    using Microsoft.CodeAnalysis.CSharp.Syntax;
+                    {code}
+                    {nspaceEnd}
+                ";
+        }
+
+        var assemblies = new List<Assembly> { Assembly.GetAssembly(typeof(MemberDeclarationSyntax))! };
+
+        if (includeOptionValidatorReferences)
+        {
+            assemblies.Add(Assembly.GetAssembly(typeof(OptionsValidatorAttribute))!);
+        }
+
+        if (includeSystemReferences)
+        {
+            assemblies.Add(Assembly.GetAssembly(typeof(RequiredAttribute))!);
+        }
+
+        if (includeOptionsReferences)
+        {
+            assemblies.Add(Assembly.GetAssembly(typeof(IValidateOptions<object>))!);
+        }
+
+        if (includeTransitiveReferences)
+        {
+            assemblies.Add(Assembly.GetAssembly(typeof(Microsoft.Extensions.Options.ValidateObjectMembersAttribute))!);
+        }
+
+        var result = await RoslynTestUtils.RunGenerator(new Generator(), assemblies.ToArray(), new[] { text })
+            .ConfigureAwait(false);
+
+        return result;
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Microsoft.Extensions.Options.SourceGeneration.Unit.Tests.csproj b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Microsoft.Extensions.Options.SourceGeneration.Unit.Tests.csproj
new file mode 100644 (file)
index 0000000..c18245a
--- /dev/null
@@ -0,0 +1,37 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFrameworks>$(NetCoreAppCurrent);$(NetFrameworkMinimum)</TargetFrameworks>
+    <RoslynApiVersion>$(MicrosoftCodeAnalysisVersion_4_4)</RoslynApiVersion>
+    <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
+    <!-- <CompilerGeneratedFilesOutputPath>$(OutputPath)/$(TargetFramework)/Generated</CompilerGeneratedFilesOutputPath> -->
+    <EnableDefaultItems>true</EnableDefaultItems>
+    <!-- <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules> -->
+    <DefineConstants>$(DefineConstants);ROSLYN4_0_OR_GREATER;ROSLYN4_4_OR_GREATER;ROSLYN_4_0_OR_GREATER</DefineConstants>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Compile Include="$(CommonPath)..\tests\SourceGenerators\RoslynTestUtils.cs" Link="SourceGenerators\RoslynTestUtils.cs" />
+    <Compile Include="$(LibrariesProjectRoot)Microsoft.Extensions.Options\gen\DiagDescriptorsBase.cs" Link="gen\DiagDescriptorsBase.cs" />
+    <Compile Include="$(LibrariesProjectRoot)Microsoft.Extensions.Options\gen\DiagDescriptors.cs" Link="gen\DiagDescriptors.cs" />
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETFramework'">
+    <ProjectReference Include="$(LibrariesProjectRoot)System.ComponentModel.Annotations\src\System.ComponentModel.Annotations.csproj" SkipUseReferenceAssembly="true" />
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
+    <Reference Include="System.ComponentModel.DataAnnotations" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.CodeAnalysis" Version="$(MicrosoftCodeAnalysisVersion)" />
+    <ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Options\src\Microsoft.Extensions.Options.csproj" SkipUseReferenceAssembly="true" />
+    <ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Options\gen\Microsoft.Extensions.Options.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="true" />
+  </ItemGroup>
+
+  <Target Name="FixIncrementalCoreCompileWithAnalyzers" BeforeTargets="CoreCompile">
+    <ItemGroup>
+      <CustomAdditionalCompileInputs Include="@(Analyzer)" />
+    </ItemGroup>
+  </Target>
+</Project>
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Resources/Strings.resx b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Resources/Strings.resx
new file mode 100644 (file)
index 0000000..1673d46
--- /dev/null
@@ -0,0 +1,201 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!--
+    Microsoft ResX Schema
+
+    Version 2.0
+
+    The primary goals of this format is to allow a simple XML format
+    that is mostly human readable. The generation and parsing of the
+    various data types are done through the TypeConverter classes
+    associated with the data types.
+
+    Example:
+
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+
+    There are any number of "resheader" rows that contain simple
+    name/value pairs.
+
+    Each data row contains a name, and value. The row also contains a
+    type or mimetype. Type corresponds to a .NET class that support
+    text/value conversion through the TypeConverter architecture.
+    Classes that don't support this are serialized and stored with the
+    mimetype set.
+
+    The mimetype is used for serialized objects, and tells the
+    ResXResourceReader how to depersist the object. This is currently not
+    extensible. For a given mimetype the value must be set accordingly:
+
+    Note - application/x-microsoft.net.object.binary.base64 is the format
+    that the ResXResourceWriter will generate, however the reader can
+    read any of the formats listed below.
+
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="ErrorMessageResourceName" xml:space="preserve">
+    <value>ErrorMessageResourceName</value>
+  </data>
+  <data name="CantUseWithGenericTypesTitle" xml:space="preserve">
+    <value>Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</value>
+  </data>
+  <data name="PotentiallyMissingEnumerableValidationMessage" xml:space="preserve">
+    <value>Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</value>
+  </data>
+  <data name="PotentiallyMissingEnumerableValidationTitle" xml:space="preserve">
+    <value>Member potentially missing enumerable validation.</value>
+  </data>
+  <data name="PotentiallyMissingTransitiveValidationMessage" xml:space="preserve">
+    <value>Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</value>
+  </data>
+  <data name="CircularTypeReferencesMessage" xml:space="preserve">
+    <value>There is a circular type reference involving type {0} preventing it from being used for static validation.</value>
+  </data>
+  <data name="CircularTypeReferencesTitle" xml:space="preserve">
+    <value>Unsupported circular references in model types.</value>
+  </data>
+  <data name="MemberIsInaccessibleMessage" xml:space="preserve">
+    <value>Can't apply validation attributes to private field or property {0}.</value>
+  </data>
+  <data name="NotEnumerableTypeMessage" xml:space="preserve">
+    <value>[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</value>
+  </data>
+  <data name="NullValidatorTypeTitle" xml:space="preserve">
+    <value>Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</value>
+  </data>
+  <data name="CantUseWithGenericTypesMessage" xml:space="preserve">
+    <value>Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</value>
+  </data>
+  <data name="NoEligibleMemberTitle" xml:space="preserve">
+    <value>A member type has no fields or properties to validate.</value>
+  </data>
+  <data name="NoEligibleMembersFromValidatorTitle" xml:space="preserve">
+    <value>A type has no fields or properties to validate.</value>
+  </data>
+  <data name="NotEnumerableTypeTitle" xml:space="preserve">
+    <value>Member type is not enumerable.</value>
+  </data>
+  <data name="ValidatorsNeedSimpleConstructorMessage" xml:space="preserve">
+    <value>Validator type {0} doesn't have a parameterless constructor.</value>
+  </data>
+  <data name="DoesntImplementIValidateOptionsMessage" xml:space="preserve">
+    <value>Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</value>
+  </data>
+  <data name="ValidatorsNeedSimpleConstructorTitle" xml:space="preserve">
+    <value>Validators used for transitive or enumerable validation must have a constructor with no parameters.</value>
+  </data>
+  <data name="NoEligibleMembersFromValidatorMessage" xml:space="preserve">
+    <value>Type {0} has no fields or properties to validate, referenced by type {1}.</value>
+  </data>
+  <data name="NoEligibleMemberMessage" xml:space="preserve">
+    <value>Type {0} has no fields or properties to validate, referenced from member {1}.</value>
+  </data>
+  <data name="DoesntImplementIValidateOptionsTitle" xml:space="preserve">
+    <value>A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</value>
+  </data>
+  <data name="MemberIsInaccessibleTitle" xml:space="preserve">
+    <value>Can't validate private fields or properties.</value>
+  </data>
+  <data name="CantBeStaticClassMessage" xml:space="preserve">
+    <value>[OptionsValidator] cannot be applied to static class {0}.</value>
+  </data>
+  <data name="AlreadyImplementsValidateMethodTitle" xml:space="preserve">
+    <value>A type already includes an implementation of the 'Validate' method.</value>
+  </data>
+  <data name="AlreadyImplementsValidateMethodMessage" xml:space="preserve">
+    <value>Type {0} already implements the Validate method.</value>
+  </data>
+  <data name="CantBeStaticClassTitle" xml:space="preserve">
+    <value>'OptionsValidatorAttribute' can't be applied to a static class.</value>
+  </data>
+  <data name="NullValidatorTypeMessage" xml:space="preserve">
+    <value>Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</value>
+  </data>
+  <data name="PotentiallyMissingTransitiveValidationTitle" xml:space="preserve">
+    <value>Member potentially missing transitive validation.</value>
+  </data>
+</root>
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetCoreApp/Validators.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetCoreApp/Validators.g.cs
new file mode 100644 (file)
index 0000000..a78ca02
--- /dev/null
@@ -0,0 +1,2080 @@
+
+    // <auto-generated/>
+    #nullable enable
+    #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+internal sealed partial class __ThirdModelNoNamespaceValidator__
+{
+    /// <summary>
+    /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+    /// </summary>
+    /// <param name="name">The name of the options instance being validated.</param>
+    /// <param name="options">The options instance.</param>
+    /// <returns>Validation result.</returns>
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::ThirdModelNoNamespace options)
+    {
+        var baseName = (string.IsNullOrEmpty(name) ? "ThirdModelNoNamespace" : name) + ".";
+        var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+        var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+        var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+        var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+        context.MemberName = "P5";
+        context.DisplayName = baseName + "P5";
+        validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+        validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+        if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
+        {
+            builder.AddResults(validationResults);
+        }
+
+        return builder.Build();
+    }
+}
+partial class FirstValidatorNoNamespace
+{
+    /// <summary>
+    /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+    /// </summary>
+    /// <param name="name">The name of the options instance being validated.</param>
+    /// <param name="options">The options instance.</param>
+    /// <returns>Validation result.</returns>
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::FirstModelNoNamespace options)
+    {
+        var baseName = (string.IsNullOrEmpty(name) ? "FirstModelNoNamespace" : name) + ".";
+        var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+        var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+        var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+        var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+        context.MemberName = "P1";
+        context.DisplayName = baseName + "P1";
+        validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+        validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+        if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+        {
+            builder.AddResults(validationResults);
+        }
+
+        if (options.P2 is not null)
+        {
+            builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V1.Validate(baseName + "P2", options.P2));
+        }
+
+        if (options.P3 is not null)
+        {
+            builder.AddResult(global::__ThirdModelNoNamespaceValidator__.Validate(baseName + "P3", options.P3));
+        }
+
+        return builder.Build();
+    }
+}
+partial class SecondValidatorNoNamespace
+{
+    /// <summary>
+    /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+    /// </summary>
+    /// <param name="name">The name of the options instance being validated.</param>
+    /// <param name="options">The options instance.</param>
+    /// <returns>Validation result.</returns>
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::SecondModelNoNamespace options)
+    {
+        var baseName = (string.IsNullOrEmpty(name) ? "SecondModelNoNamespace" : name) + ".";
+        var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+        var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+        var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+        var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+        context.MemberName = "P4";
+        context.DisplayName = baseName + "P4";
+        validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+        validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+        if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes))
+        {
+            builder.AddResults(validationResults);
+        }
+
+        return builder.Build();
+    }
+}
+namespace CustomAttr
+{
+    partial class FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::CustomAttr.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A3);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "P2";
+            context.DisplayName = baseName + "P2";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A4);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Enumeration
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __SecondModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Enumeration.SecondModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P6";
+            context.DisplayName = baseName + "P6";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Enumeration
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __ThirdModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Enumeration.ThirdModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Value";
+            context.DisplayName = baseName + "Value";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A5);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Value!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Enumeration
+{
+    partial struct FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Enumeration.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+
+            if (options.P1 is not null)
+            {
+                var count = 0;
+                foreach (var o in options.P1)
+                {
+                    if (o is not null)
+                    {
+                        builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P1[{count}]", o));
+                    }
+                    else
+                    {
+                        builder.AddError(baseName + $"P1[{count}] is null");
+                    }
+                    count++;
+                }
+            }
+
+            if (options.P2 is not null)
+            {
+                var count = 0;
+                foreach (var o in options.P2)
+                {
+                    if (o is not null)
+                    {
+                        builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V2.Validate(baseName + $"P2[{count}]", o));
+                    }
+                    else
+                    {
+                        builder.AddError(baseName + $"P2[{count}] is null");
+                    }
+                    count++;
+                }
+            }
+
+            if (options.P3 is not null)
+            {
+                var count = 0;
+                foreach (var o in options.P3)
+                {
+                    if (o is not null)
+                    {
+                        builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P3[{count}]", o));
+                    }
+                    count++;
+                }
+            }
+
+            if (options.P4 is not null)
+            {
+                var count = 0;
+                foreach (var o in options.P4)
+                {
+                    builder.AddResult(global::Enumeration.__ThirdModelValidator__.Validate(baseName + $"P4[{count++}]", o));
+                }
+            }
+
+            if (options.P5 is not null)
+            {
+                var count = 0;
+                foreach (var o in options.P5)
+                {
+                    if (o is not null)
+                    {
+                        builder.AddResult(global::Enumeration.__ThirdModelValidator__.Validate(baseName + $"P5[{count}]", o.Value));
+                    }
+                    count++;
+                }
+            }
+
+            if (options.P51 is not null)
+            {
+                var count = 0;
+                foreach (var o in options.P51)
+                {
+                    if (o is not null)
+                    {
+                        builder.AddResult(global::Enumeration.__ThirdModelValidator__.Validate(baseName + $"P51[{count}]", o.Value));
+                    }
+                    count++;
+                }
+            }
+
+            if (options.P6 is not null)
+            {
+                var count = 0;
+                foreach (var o in options.P6.Value)
+                {
+                    if (o is not null)
+                    {
+                        builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P6[{count}]", o));
+                    }
+                    else
+                    {
+                        builder.AddError(baseName + $"P6[{count}] is null");
+                    }
+                    count++;
+                }
+            }
+
+            {
+                var count = 0;
+                foreach (var o in options.P7)
+                {
+                    if (o is not null)
+                    {
+                        builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P7[{count}]", o));
+                    }
+                    else
+                    {
+                        builder.AddError(baseName + $"P7[{count}] is null");
+                    }
+                    count++;
+                }
+            }
+
+            if (options.P8 is not null)
+            {
+                var count = 0;
+                foreach (var o in options.P8.Value)
+                {
+                    if (o is not null)
+                    {
+                        builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P8[{count}]", o));
+                    }
+                    else
+                    {
+                        builder.AddError(baseName + $"P8[{count}] is null");
+                    }
+                    count++;
+                }
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Enumeration
+{
+    partial struct SecondValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Enumeration.SecondModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P6";
+            context.DisplayName = baseName + "P6";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Fields
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __ThirdModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Fields.ThirdModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P5";
+            context.DisplayName = baseName + "P5";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Fields
+{
+    partial struct FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Fields.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.P2 is not null)
+            {
+                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V3.Validate(baseName + "P2", options.P2));
+            }
+
+            builder.AddResult(global::Fields.__ThirdModelValidator__.Validate(baseName + "P3", options.P3));
+
+            return builder.Build();
+        }
+    }
+}
+namespace Fields
+{
+    partial struct SecondValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Fields.SecondModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P4";
+            context.DisplayName = baseName + "P4";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace FileScopedNamespace
+{
+    partial struct FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::FileScopedNamespace.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace FunnyStrings
+{
+    partial struct FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::FunnyStrings.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A6);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Generics
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __SecondModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Generics.SecondModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P4";
+            context.DisplayName = baseName + "P4";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Generics
+{
+    partial class FirstValidator<T>
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Generics.FirstModel<T> options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.P3 is not null)
+            {
+                builder.AddResult(global::Generics.__SecondModelValidator__.Validate(baseName + "P3", options.P3));
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace MultiModelValidator
+{
+    partial struct MultiValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::MultiModelValidator.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.P2 is not null)
+            {
+                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V4.Validate(baseName + "P2", options.P2));
+            }
+
+            return builder.Build();
+        }
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::MultiModelValidator.SecondModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P3";
+            context.DisplayName = baseName + "P3";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Nested
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __ThirdModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Nested.Container1.ThirdModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P6";
+            context.DisplayName = baseName + "P6";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Nested
+{
+    partial record struct Container7 
+    {
+        partial record struct FifthValidator
+        {
+            /// <summary>
+            /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+            /// </summary>
+            /// <param name="name">The name of the options instance being validated.</param>
+            /// <param name="options">The options instance.</param>
+            /// <returns>Validation result.</returns>
+            [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+            public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Nested.Container1.SecondModel options)
+            {
+                var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+                var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+                var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+                var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+                var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+                context.MemberName = "P5";
+                context.DisplayName = baseName + "P5";
+                validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+                validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+                if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
+                {
+                    builder.AddResults(validationResults);
+                }
+
+                return builder.Build();
+            }
+        }
+    }
+}
+namespace Nested
+{
+    partial class Container2 
+    {
+        partial class Container3 
+        {
+            partial struct FirstValidator
+            {
+                /// <summary>
+                /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+                /// </summary>
+                /// <param name="name">The name of the options instance being validated.</param>
+                /// <param name="options">The options instance.</param>
+                /// <returns>Validation result.</returns>
+                [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+                public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Nested.Container1.FirstModel options)
+                {
+                    var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+                    var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+                    var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+                    var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+                    var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+                    context.MemberName = "P1";
+                    context.DisplayName = baseName + "P1";
+                    validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+                    validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+                    if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+                    {
+                        builder.AddResults(validationResults);
+                    }
+
+                    if (options.P2 is not null)
+                    {
+                        builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V5.Validate(baseName + "P2", options.P2));
+                    }
+
+                    builder.AddResult(global::Nested.__ThirdModelValidator__.Validate(baseName + "P3", options.P3));
+
+                    if (options.P4 is not null)
+                    {
+                        builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V6.Validate(baseName + "P4", options.P4));
+                    }
+
+                    return builder.Build();
+                }
+            }
+        }
+    }
+}
+namespace Nested
+{
+    partial struct Container6 
+    {
+        partial struct FourthValidator
+        {
+            /// <summary>
+            /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+            /// </summary>
+            /// <param name="name">The name of the options instance being validated.</param>
+            /// <param name="options">The options instance.</param>
+            /// <returns>Validation result.</returns>
+            [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+            public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Nested.Container1.SecondModel options)
+            {
+                var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+                var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+                var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+                var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+                var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+                context.MemberName = "P5";
+                context.DisplayName = baseName + "P5";
+                validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+                validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+                if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
+                {
+                    builder.AddResults(validationResults);
+                }
+
+                return builder.Build();
+            }
+        }
+    }
+}
+namespace Nested
+{
+    partial class Container2 
+    {
+        partial class Container3 
+        {
+            partial struct SecondValidator
+            {
+                /// <summary>
+                /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+                /// </summary>
+                /// <param name="name">The name of the options instance being validated.</param>
+                /// <param name="options">The options instance.</param>
+                /// <returns>Validation result.</returns>
+                [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+                public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Nested.Container1.SecondModel options)
+                {
+                    var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+                    var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+                    var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+                    var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+                    var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+                    context.MemberName = "P5";
+                    context.DisplayName = baseName + "P5";
+                    validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+                    validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+                    if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
+                    {
+                        builder.AddResults(validationResults);
+                    }
+
+                    return builder.Build();
+                }
+            }
+        }
+    }
+}
+namespace Nested
+{
+    partial record class Container4 
+    {
+        partial record class Container5 
+        {
+            partial struct ThirdValidator
+            {
+                /// <summary>
+                /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+                /// </summary>
+                /// <param name="name">The name of the options instance being validated.</param>
+                /// <param name="options">The options instance.</param>
+                /// <returns>Validation result.</returns>
+                [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+                public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Nested.Container1.SecondModel options)
+                {
+                    var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+                    var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+                    var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+                    var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+                    var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+                    context.MemberName = "P5";
+                    context.DisplayName = baseName + "P5";
+                    validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+                    validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+                    if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
+                    {
+                        builder.AddResults(validationResults);
+                    }
+
+                    return builder.Build();
+                }
+            }
+        }
+    }
+}
+namespace RandomMembers
+{
+    partial class FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RandomMembers.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace RecordTypes
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __ThirdModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RecordTypes.ThirdModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P6";
+            context.DisplayName = baseName + "P6";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace RecordTypes
+{
+    partial record struct FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RecordTypes.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.P2 is not null)
+            {
+                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V7.Validate(baseName + "P2", options.P2));
+            }
+
+            if (options.P3 is not null)
+            {
+                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V8.Validate(baseName + "P3", options.P3));
+            }
+
+            builder.AddResult(global::RecordTypes.__ThirdModelValidator__.Validate(baseName + "P4", options.P4));
+
+            return builder.Build();
+        }
+    }
+}
+namespace RecordTypes
+{
+    partial record struct SecondValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RecordTypes.SecondModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P5";
+            context.DisplayName = baseName + "P5";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace RecordTypes
+{
+    partial record class ThirdValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RecordTypes.SecondModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P5";
+            context.DisplayName = baseName + "P5";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace RepeatedTypes
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __SecondModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RepeatedTypes.SecondModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "P4";
+            context.DisplayName = baseName + "P4";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.P4 is not null)
+            {
+                builder.AddResult(global::RepeatedTypes.__ThirdModelValidator__.Validate(baseName + "P4", options.P4));
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace RepeatedTypes
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __ThirdModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RepeatedTypes.ThirdModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P5";
+            context.DisplayName = baseName + "P5";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace RepeatedTypes
+{
+    partial class FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RepeatedTypes.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.P1 is not null)
+            {
+                builder.AddResult(global::RepeatedTypes.__SecondModelValidator__.Validate(baseName + "P1", options.P1));
+            }
+
+            context.MemberName = "P2";
+            context.DisplayName = baseName + "P2";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.P2 is not null)
+            {
+                builder.AddResult(global::RepeatedTypes.__SecondModelValidator__.Validate(baseName + "P2", options.P2));
+            }
+
+            context.MemberName = "P3";
+            context.DisplayName = baseName + "P3";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.P3 is not null)
+            {
+                builder.AddResult(global::RepeatedTypes.__ThirdModelValidator__.Validate(baseName + "P3", options.P3));
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace SelfValidation
+{
+    partial struct FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::SelfValidation.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            builder.AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context));
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __RangeAttributeModelDoubleValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RangeAttributeModelDouble options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "RangeAttributeModelDouble" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A7);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __RequiredAttributeModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RequiredAttributeModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "RequiredAttributeModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __TypeWithoutOptionsValidatorValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.TypeWithoutOptionsValidator options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "TypeWithoutOptionsValidator" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val1";
+            context.DisplayName = baseName + "Val1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "Val2";
+            context.DisplayName = baseName + "Val2";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A8);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val2!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.YetAnotherComplexVal is not null)
+            {
+                builder.AddResult(global::TestClasses.OptionsValidation.__RangeAttributeModelDoubleValidator__.Validate(baseName + "YetAnotherComplexVal", options.YetAnotherComplexVal));
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class AttributePropertyModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.AttributePropertyModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "AttributePropertyModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val1";
+            context.DisplayName = baseName + "Val1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A9);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "Val2";
+            context.DisplayName = baseName + "Val2";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A10);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val2!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class ComplexModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.ComplexModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "ComplexModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+
+            if (options.ComplexVal is not null)
+            {
+                builder.AddResult(global::TestClasses.OptionsValidation.__RequiredAttributeModelValidator__.Validate(baseName + "ComplexVal", options.ComplexVal));
+            }
+
+            if (options.ValWithoutOptionsValidator is not null)
+            {
+                builder.AddResult(global::TestClasses.OptionsValidation.__TypeWithoutOptionsValidatorValidator__.Validate(baseName + "ValWithoutOptionsValidator", options.ValWithoutOptionsValidator));
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class CustomTypeCustomValidationAttributeModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.CustomTypeCustomValidationAttributeModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "CustomTypeCustomValidationAttributeModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A11);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class CustomValidationAttributeModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.CustomValidationAttributeModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "CustomValidationAttributeModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A12);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class DataTypeAttributeModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.DataTypeAttributeModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "DataTypeAttributeModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A13);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class DerivedModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.DerivedModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "DerivedModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "DerivedVal";
+            context.DisplayName = baseName + "DerivedVal";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.DerivedVal!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "VirtualValWithAttr";
+            context.DisplayName = baseName + "VirtualValWithAttr";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.VirtualValWithAttr!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class EmailAttributeModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.EmailAttributeModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "EmailAttributeModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A14);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class LeafModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.LeafModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "LeafModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "VirtualValWithoutAttr";
+            context.DisplayName = baseName + "VirtualValWithoutAttr";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.VirtualValWithoutAttr!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "DerivedVal";
+            context.DisplayName = baseName + "DerivedVal";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.DerivedVal!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class MultipleAttributeModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.MultipleAttributeModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "MultipleAttributeModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "Val1";
+            context.DisplayName = baseName + "Val1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A15);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "Val2";
+            context.DisplayName = baseName + "Val2";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A16);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val2!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "Val3";
+            context.DisplayName = baseName + "Val3";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A17);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val3!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "Val4";
+            context.DisplayName = baseName + "Val4";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A18);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val4!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class RangeAttributeModelDateValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RangeAttributeModelDate options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "RangeAttributeModelDate" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A19);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class RangeAttributeModelDoubleValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RangeAttributeModelDouble options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "RangeAttributeModelDouble" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A7);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class RangeAttributeModelIntValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RangeAttributeModelInt options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "RangeAttributeModelInt" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A16);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class RegularExpressionAttributeModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RegularExpressionAttributeModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "RegularExpressionAttributeModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A20);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class RequiredAttributeModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RequiredAttributeModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "RequiredAttributeModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace ValueTypes
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __SecondModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::ValueTypes.SecondModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P4";
+            context.DisplayName = baseName + "P4";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace ValueTypes
+{
+    partial struct FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::ValueTypes.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.P2 is not null)
+            {
+                builder.AddResult(global::ValueTypes.__SecondModelValidator__.Validate(baseName + "P2", options.P2.Value));
+            }
+
+            builder.AddResult(global::ValueTypes.__SecondModelValidator__.Validate(baseName + "P3", options.P3));
+
+            if (options.P4 is not null)
+            {
+                builder.AddResult(global::ValueTypes.__SecondModelValidator__.Validate(baseName + "P4", options.P4.Value));
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal static class __Attributes
+    {
+        internal static readonly global::System.ComponentModel.DataAnnotations.RequiredAttribute A1 = new global::System.ComponentModel.DataAnnotations.RequiredAttribute();
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.MinLengthAttribute A2 = new global::System.ComponentModel.DataAnnotations.MinLengthAttribute(
+            (int)5);
+
+        internal static readonly global::CustomAttr.CustomAttribute A3 = new global::CustomAttr.CustomAttribute(
+            'A',
+            true,
+            null);
+
+        internal static readonly global::CustomAttr.CustomAttribute A4 = new global::CustomAttr.CustomAttribute(
+            'A',
+            false,
+            "X");
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A5 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+            (int)0,
+            (int)10);
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute A6 = new global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute(
+            "\"\r\n\\\\");
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A7 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+            (double)0.5,
+            (double)0.9);
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A8 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+            typeof(global::System.DateTime),
+            "1/2/2004",
+            "3/4/2004");
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A9 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+            (int)1,
+            (int)3)
+        {
+            ErrorMessage = "ErrorMessage"
+        };
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A10 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+            (int)1,
+            (int)3)
+        {
+            ErrorMessageResourceName = "ErrorMessageResourceName",
+            ErrorMessageResourceType = typeof(global::System.SR)
+        };
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.CustomValidationAttribute A11 = new global::System.ComponentModel.DataAnnotations.CustomValidationAttribute(
+            typeof(global::TestClasses.OptionsValidation.CustomTypeCustomValidationTest),
+            "TestMethod");
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.CustomValidationAttribute A12 = new global::System.ComponentModel.DataAnnotations.CustomValidationAttribute(
+            typeof(global::TestClasses.OptionsValidation.CustomValidationTest),
+            "TestMethod");
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.DataTypeAttribute A13 = new global::System.ComponentModel.DataAnnotations.DataTypeAttribute(
+            (global::System.ComponentModel.DataAnnotations.DataType)7);
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.EmailAddressAttribute A14 = new global::System.ComponentModel.DataAnnotations.EmailAddressAttribute();
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.DataTypeAttribute A15 = new global::System.ComponentModel.DataAnnotations.DataTypeAttribute(
+            (global::System.ComponentModel.DataAnnotations.DataType)11);
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A16 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+            (int)1,
+            (int)3);
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A17 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+            (int)3,
+            (int)5);
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A18 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+            (int)5,
+            (int)9);
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A19 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+            typeof(global::System.DateTime),
+            "1/2/2004",
+            "3/4/2004")
+        {
+            ParseLimitsInInvariantCulture = true
+        };
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute A20 = new global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute(
+            "\\s");
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal static class __Validators
+    {
+        internal static readonly global::SecondValidatorNoNamespace V1 = new global::SecondValidatorNoNamespace();
+
+        internal static readonly global::Enumeration.SecondValidator V2 = new global::Enumeration.SecondValidator();
+
+        internal static readonly global::Fields.SecondValidator V3 = new global::Fields.SecondValidator();
+
+        internal static readonly global::MultiModelValidator.MultiValidator V4 = new global::MultiModelValidator.MultiValidator();
+
+        internal static readonly global::Nested.Container2.Container3.SecondValidator V5 = new global::Nested.Container2.Container3.SecondValidator();
+
+        internal static readonly global::Nested.Container4.Container5.ThirdValidator V6 = new global::Nested.Container4.Container5.ThirdValidator();
+
+        internal static readonly global::RecordTypes.SecondValidator V7 = new global::RecordTypes.SecondValidator();
+
+        internal static readonly global::RecordTypes.ThirdValidator V8 = new global::RecordTypes.ThirdValidator();
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetFX/Validators.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetFX/Validators.g.cs
new file mode 100644 (file)
index 0000000..daa6511
--- /dev/null
@@ -0,0 +1,2072 @@
+
+    // <auto-generated/>
+    #nullable enable
+    #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+internal sealed partial class __ThirdModelNoNamespaceValidator__
+{
+    /// <summary>
+    /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+    /// </summary>
+    /// <param name="name">The name of the options instance being validated.</param>
+    /// <param name="options">The options instance.</param>
+    /// <returns>Validation result.</returns>
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::ThirdModelNoNamespace options)
+    {
+        var baseName = (string.IsNullOrEmpty(name) ? "ThirdModelNoNamespace" : name) + ".";
+        var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+        var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+        var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+        var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+        context.MemberName = "P5";
+        context.DisplayName = baseName + "P5";
+        validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+        validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+        if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
+        {
+            builder.AddResults(validationResults);
+        }
+
+        return builder.Build();
+    }
+}
+partial class FirstValidatorNoNamespace
+{
+    /// <summary>
+    /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+    /// </summary>
+    /// <param name="name">The name of the options instance being validated.</param>
+    /// <param name="options">The options instance.</param>
+    /// <returns>Validation result.</returns>
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::FirstModelNoNamespace options)
+    {
+        var baseName = (string.IsNullOrEmpty(name) ? "FirstModelNoNamespace" : name) + ".";
+        var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+        var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+        var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+        var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+        context.MemberName = "P1";
+        context.DisplayName = baseName + "P1";
+        validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+        validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+        if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+        {
+            builder.AddResults(validationResults);
+        }
+
+        if (options.P2 is not null)
+        {
+            builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V1.Validate(baseName + "P2", options.P2));
+        }
+
+        if (options.P3 is not null)
+        {
+            builder.AddResult(global::__ThirdModelNoNamespaceValidator__.Validate(baseName + "P3", options.P3));
+        }
+
+        return builder.Build();
+    }
+}
+partial class SecondValidatorNoNamespace
+{
+    /// <summary>
+    /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+    /// </summary>
+    /// <param name="name">The name of the options instance being validated.</param>
+    /// <param name="options">The options instance.</param>
+    /// <returns>Validation result.</returns>
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::SecondModelNoNamespace options)
+    {
+        var baseName = (string.IsNullOrEmpty(name) ? "SecondModelNoNamespace" : name) + ".";
+        var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+        var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+        var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+        var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+        context.MemberName = "P4";
+        context.DisplayName = baseName + "P4";
+        validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+        validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+        if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes))
+        {
+            builder.AddResults(validationResults);
+        }
+
+        return builder.Build();
+    }
+}
+namespace CustomAttr
+{
+    partial class FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::CustomAttr.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A3);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "P2";
+            context.DisplayName = baseName + "P2";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A4);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Enumeration
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __SecondModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Enumeration.SecondModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P6";
+            context.DisplayName = baseName + "P6";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Enumeration
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __ThirdModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Enumeration.ThirdModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Value";
+            context.DisplayName = baseName + "Value";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A5);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Value!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Enumeration
+{
+    partial struct FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Enumeration.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+
+            if (options.P1 is not null)
+            {
+                var count = 0;
+                foreach (var o in options.P1)
+                {
+                    if (o is not null)
+                    {
+                        builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P1[{count}]", o));
+                    }
+                    else
+                    {
+                        builder.AddError(baseName + $"P1[{count}] is null");
+                    }
+                    count++;
+                }
+            }
+
+            if (options.P2 is not null)
+            {
+                var count = 0;
+                foreach (var o in options.P2)
+                {
+                    if (o is not null)
+                    {
+                        builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V2.Validate(baseName + $"P2[{count}]", o));
+                    }
+                    else
+                    {
+                        builder.AddError(baseName + $"P2[{count}] is null");
+                    }
+                    count++;
+                }
+            }
+
+            if (options.P3 is not null)
+            {
+                var count = 0;
+                foreach (var o in options.P3)
+                {
+                    if (o is not null)
+                    {
+                        builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P3[{count}]", o));
+                    }
+                    count++;
+                }
+            }
+
+            if (options.P4 is not null)
+            {
+                var count = 0;
+                foreach (var o in options.P4)
+                {
+                    builder.AddResult(global::Enumeration.__ThirdModelValidator__.Validate(baseName + $"P4[{count++}]", o));
+                }
+            }
+
+            if (options.P5 is not null)
+            {
+                var count = 0;
+                foreach (var o in options.P5)
+                {
+                    if (o is not null)
+                    {
+                        builder.AddResult(global::Enumeration.__ThirdModelValidator__.Validate(baseName + $"P5[{count}]", o.Value));
+                    }
+                    count++;
+                }
+            }
+
+            if (options.P51 is not null)
+            {
+                var count = 0;
+                foreach (var o in options.P51)
+                {
+                    if (o is not null)
+                    {
+                        builder.AddResult(global::Enumeration.__ThirdModelValidator__.Validate(baseName + $"P51[{count}]", o.Value));
+                    }
+                    count++;
+                }
+            }
+
+            if (options.P6 is not null)
+            {
+                var count = 0;
+                foreach (var o in options.P6.Value)
+                {
+                    if (o is not null)
+                    {
+                        builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P6[{count}]", o));
+                    }
+                    else
+                    {
+                        builder.AddError(baseName + $"P6[{count}] is null");
+                    }
+                    count++;
+                }
+            }
+
+            {
+                var count = 0;
+                foreach (var o in options.P7)
+                {
+                    if (o is not null)
+                    {
+                        builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P7[{count}]", o));
+                    }
+                    else
+                    {
+                        builder.AddError(baseName + $"P7[{count}] is null");
+                    }
+                    count++;
+                }
+            }
+
+            if (options.P8 is not null)
+            {
+                var count = 0;
+                foreach (var o in options.P8.Value)
+                {
+                    if (o is not null)
+                    {
+                        builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P8[{count}]", o));
+                    }
+                    else
+                    {
+                        builder.AddError(baseName + $"P8[{count}] is null");
+                    }
+                    count++;
+                }
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Enumeration
+{
+    partial struct SecondValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Enumeration.SecondModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P6";
+            context.DisplayName = baseName + "P6";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Fields
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __ThirdModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Fields.ThirdModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P5";
+            context.DisplayName = baseName + "P5";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Fields
+{
+    partial struct FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Fields.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.P2 is not null)
+            {
+                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V3.Validate(baseName + "P2", options.P2));
+            }
+
+            builder.AddResult(global::Fields.__ThirdModelValidator__.Validate(baseName + "P3", options.P3));
+
+            return builder.Build();
+        }
+    }
+}
+namespace Fields
+{
+    partial struct SecondValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Fields.SecondModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P4";
+            context.DisplayName = baseName + "P4";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace FileScopedNamespace
+{
+    partial struct FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::FileScopedNamespace.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace FunnyStrings
+{
+    partial struct FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::FunnyStrings.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A6);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Generics
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __SecondModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Generics.SecondModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P4";
+            context.DisplayName = baseName + "P4";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Generics
+{
+    partial class FirstValidator<T>
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Generics.FirstModel<T> options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.P3 is not null)
+            {
+                builder.AddResult(global::Generics.__SecondModelValidator__.Validate(baseName + "P3", options.P3));
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace MultiModelValidator
+{
+    partial struct MultiValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::MultiModelValidator.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.P2 is not null)
+            {
+                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V4.Validate(baseName + "P2", options.P2));
+            }
+
+            return builder.Build();
+        }
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::MultiModelValidator.SecondModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P3";
+            context.DisplayName = baseName + "P3";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Nested
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __ThirdModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Nested.Container1.ThirdModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P6";
+            context.DisplayName = baseName + "P6";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace Nested
+{
+    partial record struct Container7 
+    {
+        partial record struct FifthValidator
+        {
+            /// <summary>
+            /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+            /// </summary>
+            /// <param name="name">The name of the options instance being validated.</param>
+            /// <param name="options">The options instance.</param>
+            /// <returns>Validation result.</returns>
+            [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+            public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Nested.Container1.SecondModel options)
+            {
+                var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+                var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+                var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+                var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+                var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+                context.MemberName = "P5";
+                context.DisplayName = baseName + "P5";
+                validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+                validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+                if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
+                {
+                    builder.AddResults(validationResults);
+                }
+
+                return builder.Build();
+            }
+        }
+    }
+}
+namespace Nested
+{
+    partial class Container2 
+    {
+        partial class Container3 
+        {
+            partial struct FirstValidator
+            {
+                /// <summary>
+                /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+                /// </summary>
+                /// <param name="name">The name of the options instance being validated.</param>
+                /// <param name="options">The options instance.</param>
+                /// <returns>Validation result.</returns>
+                [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+                public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Nested.Container1.FirstModel options)
+                {
+                    var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+                    var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+                    var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+                    var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+                    var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+                    context.MemberName = "P1";
+                    context.DisplayName = baseName + "P1";
+                    validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+                    validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+                    if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+                    {
+                        builder.AddResults(validationResults);
+                    }
+
+                    if (options.P2 is not null)
+                    {
+                        builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V5.Validate(baseName + "P2", options.P2));
+                    }
+
+                    builder.AddResult(global::Nested.__ThirdModelValidator__.Validate(baseName + "P3", options.P3));
+
+                    if (options.P4 is not null)
+                    {
+                        builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V6.Validate(baseName + "P4", options.P4));
+                    }
+
+                    return builder.Build();
+                }
+            }
+        }
+    }
+}
+namespace Nested
+{
+    partial struct Container6 
+    {
+        partial struct FourthValidator
+        {
+            /// <summary>
+            /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+            /// </summary>
+            /// <param name="name">The name of the options instance being validated.</param>
+            /// <param name="options">The options instance.</param>
+            /// <returns>Validation result.</returns>
+            [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+            public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Nested.Container1.SecondModel options)
+            {
+                var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+                var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+                var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+                var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+                var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+                context.MemberName = "P5";
+                context.DisplayName = baseName + "P5";
+                validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+                validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+                if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
+                {
+                    builder.AddResults(validationResults);
+                }
+
+                return builder.Build();
+            }
+        }
+    }
+}
+namespace Nested
+{
+    partial class Container2 
+    {
+        partial class Container3 
+        {
+            partial struct SecondValidator
+            {
+                /// <summary>
+                /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+                /// </summary>
+                /// <param name="name">The name of the options instance being validated.</param>
+                /// <param name="options">The options instance.</param>
+                /// <returns>Validation result.</returns>
+                [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+                public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Nested.Container1.SecondModel options)
+                {
+                    var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+                    var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+                    var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+                    var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+                    var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+                    context.MemberName = "P5";
+                    context.DisplayName = baseName + "P5";
+                    validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+                    validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+                    if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
+                    {
+                        builder.AddResults(validationResults);
+                    }
+
+                    return builder.Build();
+                }
+            }
+        }
+    }
+}
+namespace Nested
+{
+    partial record class Container4 
+    {
+        partial record class Container5 
+        {
+            partial struct ThirdValidator
+            {
+                /// <summary>
+                /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+                /// </summary>
+                /// <param name="name">The name of the options instance being validated.</param>
+                /// <param name="options">The options instance.</param>
+                /// <returns>Validation result.</returns>
+                [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+                public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Nested.Container1.SecondModel options)
+                {
+                    var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+                    var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+                    var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+                    var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+                    var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+                    context.MemberName = "P5";
+                    context.DisplayName = baseName + "P5";
+                    validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+                    validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+                    if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
+                    {
+                        builder.AddResults(validationResults);
+                    }
+
+                    return builder.Build();
+                }
+            }
+        }
+    }
+}
+namespace RandomMembers
+{
+    partial class FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RandomMembers.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace RecordTypes
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __ThirdModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RecordTypes.ThirdModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P6";
+            context.DisplayName = baseName + "P6";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace RecordTypes
+{
+    partial record struct FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RecordTypes.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.P2 is not null)
+            {
+                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V7.Validate(baseName + "P2", options.P2));
+            }
+
+            if (options.P3 is not null)
+            {
+                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V8.Validate(baseName + "P3", options.P3));
+            }
+
+            builder.AddResult(global::RecordTypes.__ThirdModelValidator__.Validate(baseName + "P4", options.P4));
+
+            return builder.Build();
+        }
+    }
+}
+namespace RecordTypes
+{
+    partial record struct SecondValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RecordTypes.SecondModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P5";
+            context.DisplayName = baseName + "P5";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace RecordTypes
+{
+    partial record class ThirdValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RecordTypes.SecondModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P5";
+            context.DisplayName = baseName + "P5";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace RepeatedTypes
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __SecondModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RepeatedTypes.SecondModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "P4";
+            context.DisplayName = baseName + "P4";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.P4 is not null)
+            {
+                builder.AddResult(global::RepeatedTypes.__ThirdModelValidator__.Validate(baseName + "P4", options.P4));
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace RepeatedTypes
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __ThirdModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RepeatedTypes.ThirdModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P5";
+            context.DisplayName = baseName + "P5";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace RepeatedTypes
+{
+    partial class FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RepeatedTypes.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.P1 is not null)
+            {
+                builder.AddResult(global::RepeatedTypes.__SecondModelValidator__.Validate(baseName + "P1", options.P1));
+            }
+
+            context.MemberName = "P2";
+            context.DisplayName = baseName + "P2";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.P2 is not null)
+            {
+                builder.AddResult(global::RepeatedTypes.__SecondModelValidator__.Validate(baseName + "P2", options.P2));
+            }
+
+            context.MemberName = "P3";
+            context.DisplayName = baseName + "P3";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.P3 is not null)
+            {
+                builder.AddResult(global::RepeatedTypes.__ThirdModelValidator__.Validate(baseName + "P3", options.P3));
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace SelfValidation
+{
+    partial struct FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::SelfValidation.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            builder.AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context));
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __RangeAttributeModelDoubleValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RangeAttributeModelDouble options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "RangeAttributeModelDouble" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A7);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __RequiredAttributeModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RequiredAttributeModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "RequiredAttributeModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __TypeWithoutOptionsValidatorValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.TypeWithoutOptionsValidator options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "TypeWithoutOptionsValidator" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val1";
+            context.DisplayName = baseName + "Val1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "Val2";
+            context.DisplayName = baseName + "Val2";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A8);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val2!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.YetAnotherComplexVal is not null)
+            {
+                builder.AddResult(global::TestClasses.OptionsValidation.__RangeAttributeModelDoubleValidator__.Validate(baseName + "YetAnotherComplexVal", options.YetAnotherComplexVal));
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class AttributePropertyModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.AttributePropertyModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "AttributePropertyModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val1";
+            context.DisplayName = baseName + "Val1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A9);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "Val2";
+            context.DisplayName = baseName + "Val2";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A10);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val2!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class ComplexModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.ComplexModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "ComplexModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+
+            if (options.ComplexVal is not null)
+            {
+                builder.AddResult(global::TestClasses.OptionsValidation.__RequiredAttributeModelValidator__.Validate(baseName + "ComplexVal", options.ComplexVal));
+            }
+
+            if (options.ValWithoutOptionsValidator is not null)
+            {
+                builder.AddResult(global::TestClasses.OptionsValidation.__TypeWithoutOptionsValidatorValidator__.Validate(baseName + "ValWithoutOptionsValidator", options.ValWithoutOptionsValidator));
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class CustomTypeCustomValidationAttributeModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.CustomTypeCustomValidationAttributeModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "CustomTypeCustomValidationAttributeModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A11);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class CustomValidationAttributeModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.CustomValidationAttributeModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "CustomValidationAttributeModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A12);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class DataTypeAttributeModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.DataTypeAttributeModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "DataTypeAttributeModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A13);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class DerivedModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.DerivedModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "DerivedModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "DerivedVal";
+            context.DisplayName = baseName + "DerivedVal";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.DerivedVal!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "VirtualValWithAttr";
+            context.DisplayName = baseName + "VirtualValWithAttr";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.VirtualValWithAttr!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class EmailAttributeModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.EmailAttributeModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "EmailAttributeModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A14);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class LeafModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.LeafModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "LeafModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "VirtualValWithoutAttr";
+            context.DisplayName = baseName + "VirtualValWithoutAttr";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.VirtualValWithoutAttr!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "DerivedVal";
+            context.DisplayName = baseName + "DerivedVal";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.DerivedVal!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class MultipleAttributeModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.MultipleAttributeModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "MultipleAttributeModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "Val1";
+            context.DisplayName = baseName + "Val1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A15);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "Val2";
+            context.DisplayName = baseName + "Val2";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A16);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val2!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "Val3";
+            context.DisplayName = baseName + "Val3";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A17);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val3!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            context.MemberName = "Val4";
+            context.DisplayName = baseName + "Val4";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A18);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val4!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class RangeAttributeModelDateValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RangeAttributeModelDate options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "RangeAttributeModelDate" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A8);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class RangeAttributeModelDoubleValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RangeAttributeModelDouble options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "RangeAttributeModelDouble" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A7);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class RangeAttributeModelIntValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RangeAttributeModelInt options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "RangeAttributeModelInt" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A16);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class RegularExpressionAttributeModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RegularExpressionAttributeModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "RegularExpressionAttributeModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A19);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace TestClasses.OptionsValidation
+{
+    partial class RequiredAttributeModelValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RequiredAttributeModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "RequiredAttributeModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val";
+            context.DisplayName = baseName + "Val";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace ValueTypes
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal sealed partial class __SecondModelValidator__
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::ValueTypes.SecondModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P4";
+            context.DisplayName = baseName + "P4";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace ValueTypes
+{
+    partial struct FirstValidator
+    {
+        /// <summary>
+        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
+        /// </summary>
+        /// <param name="name">The name of the options instance being validated.</param>
+        /// <param name="options">The options instance.</param>
+        /// <returns>Validation result.</returns>
+        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::ValueTypes.FirstModel options)
+        {
+            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
+            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
+            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
+            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
+
+            context.MemberName = "P1";
+            context.DisplayName = baseName + "P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
+            {
+                builder.AddResults(validationResults);
+            }
+
+            if (options.P2 is not null)
+            {
+                builder.AddResult(global::ValueTypes.__SecondModelValidator__.Validate(baseName + "P2", options.P2.Value));
+            }
+
+            builder.AddResult(global::ValueTypes.__SecondModelValidator__.Validate(baseName + "P3", options.P3));
+
+            if (options.P4 is not null)
+            {
+                builder.AddResult(global::ValueTypes.__SecondModelValidator__.Validate(baseName + "P4", options.P4.Value));
+            }
+
+            return builder.Build();
+        }
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal static class __Attributes
+    {
+        internal static readonly global::System.ComponentModel.DataAnnotations.RequiredAttribute A1 = new global::System.ComponentModel.DataAnnotations.RequiredAttribute();
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.MinLengthAttribute A2 = new global::System.ComponentModel.DataAnnotations.MinLengthAttribute(
+            (int)5);
+
+        internal static readonly global::CustomAttr.CustomAttribute A3 = new global::CustomAttr.CustomAttribute(
+            'A',
+            true,
+            null);
+
+        internal static readonly global::CustomAttr.CustomAttribute A4 = new global::CustomAttr.CustomAttribute(
+            'A',
+            false,
+            "X");
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A5 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+            (int)0,
+            (int)10);
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute A6 = new global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute(
+            "\"\r\n\\\\");
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A7 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+            (double)0.5,
+            (double)0.9);
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A8 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+            typeof(global::System.DateTime),
+            "1/2/2004",
+            "3/4/2004");
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A9 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+            (int)1,
+            (int)3)
+        {
+            ErrorMessage = "ErrorMessage"
+        };
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A10 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+            (int)1,
+            (int)3)
+        {
+            ErrorMessageResourceName = "ErrorMessageResourceName",
+            ErrorMessageResourceType = typeof(global::System.SR)
+        };
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.CustomValidationAttribute A11 = new global::System.ComponentModel.DataAnnotations.CustomValidationAttribute(
+            typeof(global::TestClasses.OptionsValidation.CustomTypeCustomValidationTest),
+            "TestMethod");
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.CustomValidationAttribute A12 = new global::System.ComponentModel.DataAnnotations.CustomValidationAttribute(
+            typeof(global::TestClasses.OptionsValidation.CustomValidationTest),
+            "TestMethod");
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.DataTypeAttribute A13 = new global::System.ComponentModel.DataAnnotations.DataTypeAttribute(
+            (global::System.ComponentModel.DataAnnotations.DataType)7);
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.EmailAddressAttribute A14 = new global::System.ComponentModel.DataAnnotations.EmailAddressAttribute();
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.DataTypeAttribute A15 = new global::System.ComponentModel.DataAnnotations.DataTypeAttribute(
+            (global::System.ComponentModel.DataAnnotations.DataType)11);
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A16 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+            (int)1,
+            (int)3);
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A17 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+            (int)3,
+            (int)5);
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A18 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+            (int)5,
+            (int)9);
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute A19 = new global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute(
+            "\\s");
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal static class __Validators
+    {
+        internal static readonly global::SecondValidatorNoNamespace V1 = new global::SecondValidatorNoNamespace();
+
+        internal static readonly global::Enumeration.SecondValidator V2 = new global::Enumeration.SecondValidator();
+
+        internal static readonly global::Fields.SecondValidator V3 = new global::Fields.SecondValidator();
+
+        internal static readonly global::MultiModelValidator.MultiValidator V4 = new global::MultiModelValidator.MultiValidator();
+
+        internal static readonly global::Nested.Container2.Container3.SecondValidator V5 = new global::Nested.Container2.Container3.SecondValidator();
+
+        internal static readonly global::Nested.Container4.Container5.ThirdValidator V6 = new global::Nested.Container4.Container5.ThirdValidator();
+
+        internal static readonly global::RecordTypes.SecondValidator V7 = new global::RecordTypes.SecondValidator();
+
+        internal static readonly global::RecordTypes.ThirdValidator V8 = new global::RecordTypes.ThirdValidator();
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/EmitterTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/EmitterTests.cs
new file mode 100644 (file)
index 0000000..fe3e007
--- /dev/null
@@ -0,0 +1,59 @@
+// 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.ComponentModel.DataAnnotations;
+using System.IO;
+using System.Reflection;
+using System.Threading.Tasks;
+using SourceGenerators.Tests;
+using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Options.Generators;
+using Microsoft.Shared.Data.Validation;
+using Xunit;
+
+namespace Microsoft.Gen.OptionsValidation.Test;
+
+public class EmitterTests
+{
+    [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    public async Task TestEmitter()
+    {
+        var sources = new List<string>();
+#pragma warning disable RS1035 // To allow using the File IO APIs inside the analyzer test
+        foreach (var file in Directory.GetFiles("TestClasses"))
+        {
+#if NETCOREAPP3_1_OR_GREATER
+            sources.Add("#define NETCOREAPP3_1_OR_GREATER\n" + File.ReadAllText(file));
+#else
+            sources.Add(File.ReadAllText(file));
+#endif
+        }
+
+        var (d, r) = await RoslynTestUtils.RunGenerator(
+            new Generator(),
+            new[]
+            {
+                Assembly.GetAssembly(typeof(RequiredAttribute))!,
+                Assembly.GetAssembly(typeof(TimeSpanAttribute))!,
+                Assembly.GetAssembly(typeof(OptionsValidatorAttribute))!,
+                Assembly.GetAssembly(typeof(IValidateOptions<object>))!,
+            },
+            sources)
+            .ConfigureAwait(false);
+
+        Assert.Empty(d);
+        _ = Assert.Single(r);
+
+#if NETCOREAPP3_1_OR_GREATER
+        string baseline = File.ReadAllText(@"Baselines/NetCoreApp/Validators.g.cs");
+#else
+        string baseline = File.ReadAllText(@"Baselines/NetFX/Validators.g.cs");
+#endif
+
+        string result = r[0].SourceText.ToString();
+        Assert.Equal(baseline, result);
+#pragma warning restore RS1035
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/EmptyReadOnlyList.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/EmptyReadOnlyList.cs
new file mode 100644 (file)
index 0000000..cf0d931
--- /dev/null
@@ -0,0 +1,58 @@
+// 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.Generic;
+
+#pragma warning disable CA1716
+namespace Microsoft.Shared.Collections;
+#pragma warning restore CA1716
+
+#if !SHARED_PROJECT
+[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+#endif
+
+[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1001:Types that own disposable fields should be disposable", Justification = "Static field, lifetime matches the process")]
+internal sealed class EmptyReadOnlyList<T> : IReadOnlyList<T>, ICollection<T>
+{
+    public static readonly EmptyReadOnlyList<T> Instance = new();
+    private readonly Enumerator _enumerator = new();
+
+    public IEnumerator<T> GetEnumerator() => _enumerator;
+    IEnumerator IEnumerable.GetEnumerator() => _enumerator;
+    public int Count => 0;
+    public T this[int index] => throw new ArgumentOutOfRangeException(nameof(index));
+
+    void ICollection<T>.CopyTo(T[] array, int arrayIndex)
+    {
+        // nop
+    }
+
+    bool ICollection<T>.Contains(T item) => false;
+    bool ICollection<T>.IsReadOnly => true;
+    void ICollection<T>.Add(T item) => throw new NotSupportedException();
+    bool ICollection<T>.Remove(T item) => false;
+
+    void ICollection<T>.Clear()
+    {
+        // nop
+    }
+
+    internal sealed class Enumerator : IEnumerator<T>
+    {
+        public void Dispose()
+        {
+            // nop
+        }
+
+        public void Reset()
+        {
+            // nop
+        }
+
+        public bool MoveNext() => false;
+        public T Current => throw new InvalidOperationException();
+        object IEnumerator.Current => throw new InvalidOperationException();
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/EmptyReadonlyDictionary.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/EmptyReadonlyDictionary.cs
new file mode 100644 (file)
index 0000000..99ef907
--- /dev/null
@@ -0,0 +1,63 @@
+// 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.Generic;
+
+#pragma warning disable CA1716
+namespace Microsoft.Shared.Collections;
+#pragma warning restore CA1716
+
+#if !SHARED_PROJECT
+[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+#endif
+
+internal sealed class EmptyReadOnlyDictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>, IDictionary<TKey, TValue>
+    where TKey : notnull
+{
+    public static readonly EmptyReadOnlyDictionary<TKey, TValue> Instance = new();
+
+    public int Count => 0;
+    public TValue this[TKey key] => throw new KeyNotFoundException();
+    public bool ContainsKey(TKey key) => false;
+    public IEnumerable<TKey> Keys => EmptyReadOnlyList<TKey>.Instance;
+    public IEnumerable<TValue> Values => EmptyReadOnlyList<TValue>.Instance;
+
+    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => EmptyReadOnlyList<KeyValuePair<TKey, TValue>>.Instance.GetEnumerator();
+    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+    ICollection<TKey> IDictionary<TKey, TValue>.Keys => Array.Empty<TKey>();
+    ICollection<TValue> IDictionary<TKey, TValue>.Values => Array.Empty<TValue>();
+    bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => true;
+    TValue IDictionary<TKey, TValue>.this[TKey key]
+    {
+        get => throw new KeyNotFoundException();
+        set => throw new NotSupportedException();
+    }
+
+    public bool TryGetValue(TKey key, out TValue value)
+    {
+#pragma warning disable CS8601 // The recommended implementation: https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2.trygetvalue
+        value = default;
+#pragma warning restore
+
+        return false;
+    }
+
+    void ICollection<KeyValuePair<TKey, TValue>>.Clear()
+    {
+        // nop
+    }
+
+    void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
+    {
+        // nop
+    }
+
+    void IDictionary<TKey, TValue>.Add(TKey key, TValue value) => throw new NotSupportedException();
+    bool IDictionary<TKey, TValue>.Remove(TKey key) => false;
+    void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) => throw new NotSupportedException();
+    bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) => false;
+    bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) => false;
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/TimeSpanAttribute.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/TimeSpanAttribute.cs
new file mode 100644 (file)
index 0000000..e1e7639
--- /dev/null
@@ -0,0 +1,160 @@
+// 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.ComponentModel.DataAnnotations;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+// using Microsoft.Shared.Diagnostics;
+
+#pragma warning disable CA1716
+namespace Microsoft.Shared.Data.Validation;
+#pragma warning restore CA1716
+
+/// <summary>
+/// Provides boundary validation for <see cref="TimeSpan"/>.
+/// </summary>
+#if !SHARED_PROJECT
+[ExcludeFromCodeCoverage]
+#endif
+
+[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
+[SuppressMessage("Design", "CA1019:Define accessors for attribute arguments", Justification = "Indirectly we are.")]
+internal sealed class TimeSpanAttribute : ValidationAttribute
+{
+    /// <summary>
+    /// Gets the lower bound for time span.
+    /// </summary>
+    public TimeSpan Minimum => _minMs.HasValue ? TimeSpan.FromMilliseconds((double)_minMs) : TimeSpan.Parse(_min!, CultureInfo.InvariantCulture);
+
+    /// <summary>
+    /// Gets the upper bound for time span.
+    /// </summary>
+    public TimeSpan? Maximum
+    {
+        get
+        {
+            if (_maxMs.HasValue)
+            {
+                return TimeSpan.FromMilliseconds((double)_maxMs);
+            }
+            else
+            {
+                return _max == null ? null : TimeSpan.Parse(_max, CultureInfo.InvariantCulture);
+            }
+        }
+    }
+
+    /// <summary>
+    /// Gets or sets a value indicating whether the time span validation should exclude the minimum and maximum values.
+    /// </summary>
+    /// <remarks>
+    /// By default the property is set to <c>false</c>.
+    /// </remarks>
+    public bool Exclusive { get; set; }
+
+    private readonly int? _minMs;
+    private readonly int? _maxMs;
+    private readonly string? _min;
+    private readonly string? _max;
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="TimeSpanAttribute"/> class.
+    /// </summary>
+    /// <param name="minMs">Minimum in milliseconds.</param>
+    public TimeSpanAttribute(int minMs)
+    {
+        _minMs = minMs;
+        _maxMs = null;
+    }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="TimeSpanAttribute"/> class.
+    /// </summary>
+    /// <param name="minMs">Minimum in milliseconds.</param>
+    /// <param name="maxMs">Maximum in milliseconds.</param>
+    public TimeSpanAttribute(int minMs, int maxMs)
+    {
+        _minMs = minMs;
+        _maxMs = maxMs;
+    }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="TimeSpanAttribute"/> class.
+    /// </summary>
+    /// <param name="min">Minimum represented as time span string.</param>
+    public TimeSpanAttribute(string min)
+    {
+        _ = ThrowHelper.IfNullOrWhitespace(min);
+
+        _min = min;
+        _max = null;
+    }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="TimeSpanAttribute"/> class.
+    /// </summary>
+    /// <param name="min">Minimum represented as time span string.</param>
+    /// <param name="max">Maximum represented as time span string.</param>
+    public TimeSpanAttribute(string min, string max)
+    {
+        _ = ThrowHelper.IfNullOrWhitespace(min);
+        _ = ThrowHelper.IfNullOrWhitespace(max);
+
+        _min = min;
+        _max = max;
+    }
+
+    /// <summary>
+    /// Validates that a given value represents an in-range TimeSpan value.
+    /// </summary>
+    /// <param name="value">The value to validate.</param>
+    /// <param name="validationContext">Additional context for this validation.</param>
+    /// <returns>A value indicating success or failure.</returns>
+    protected override ValidationResult IsValid(object? value, ValidationContext? validationContext)
+    {
+        var min = Minimum;
+        var max = Maximum;
+
+        if (min >= max)
+        {
+            throw new InvalidOperationException($"{nameof(TimeSpanAttribute)} requires that the minimum value be less than the maximum value (see field {validationContext.GetDisplayName()})");
+        }
+
+        if (value == null)
+        {
+            // use the [Required] attribute to force presence
+            return ValidationResult.Success!;
+        }
+
+        if (value is TimeSpan ts)
+        {
+            if (Exclusive && ts <= min)
+            {
+                return new ValidationResult($"The field {validationContext.GetDisplayName()} must be > to {min}.", validationContext.GetMemberName());
+            }
+
+            if (ts < min)
+            {
+                return new ValidationResult($"The field {validationContext.GetDisplayName()} must be >= to {min}.", validationContext.GetMemberName());
+            }
+
+            if (max.HasValue)
+            {
+                if (Exclusive && ts >= max.Value)
+                {
+                    return new ValidationResult($"The field {validationContext.GetDisplayName()} must be < to {max}.", validationContext.GetMemberName());
+                }
+
+                if (ts > max.Value)
+                {
+                    return new ValidationResult($"The field {validationContext.GetDisplayName()} must be <= to {max}.", validationContext.GetMemberName());
+                }
+            }
+
+            return ValidationResult.Success!;
+        }
+
+        throw new InvalidOperationException($"{nameof(TimeSpanAttribute)} can only be used with fields of type TimeSpan (see field {validationContext.GetDisplayName()})");
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/ValidationContextExtensions.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/ValidationContextExtensions.cs
new file mode 100644 (file)
index 0000000..45d3365
--- /dev/null
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.ComponentModel.DataAnnotations;
+
+#pragma warning disable CA1716
+namespace Microsoft.Shared.Data.Validation;
+#pragma warning restore CA1716
+
+#if !SHARED_PROJECT
+[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+#endif
+
+internal static class ValidationContextExtensions
+{
+    public static string[]? GetMemberName(this ValidationContext? validationContext)
+    {
+#pragma warning disable S1168 // Empty arrays and collections should be returned instead of null
+        return validationContext?.MemberName is { } memberName
+            ? new[] { memberName }
+            : null;
+#pragma warning restore S1168 // Empty arrays and collections should be returned instead of null
+    }
+
+    public static string GetDisplayName(this ValidationContext? validationContext)
+    {
+        return validationContext?.DisplayName ?? string.Empty;
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/CustomAttrTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/CustomAttrTests.cs
new file mode 100644 (file)
index 0000000..9770924
--- /dev/null
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using CustomAttr;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Microsoft.Gen.OptionsValidation.Test;
+
+public class CustomAttrTests
+{
+    [Fact]
+    public void Invalid()
+    {
+        var firstModel = new FirstModel
+        {
+            P1 = 'a',
+            P2 = 'x',
+        };
+
+        var validator = new FirstValidator();
+        var vr = validator.Validate("CustomAttr", firstModel);
+
+        Utils.VerifyValidateOptionsResult(vr, 2, "P1", "P2");
+    }
+
+    [Fact]
+    public void Valid()
+    {
+        var firstModel = new FirstModel
+        {
+            P1 = 'A',
+            P2 = 'A',
+        };
+
+        var validator = new FirstValidator();
+        Assert.Equal(ValidateOptionsResult.Success, validator.Validate("CustomAttr", firstModel));
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/EnumerationTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/EnumerationTests.cs
new file mode 100644 (file)
index 0000000..d2f9bdd
--- /dev/null
@@ -0,0 +1,93 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Enumeration;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Microsoft.Gen.OptionsValidation.Test;
+
+public class EnumerationTests
+{
+    [Fact]
+    public void Invalid()
+    {
+        var secondModelC = new SecondModel
+        {
+            P6 = "1234",
+        };
+
+        var secondModelB = new SecondModel
+        {
+            P6 = "12345",
+        };
+
+        var secondModel = new SecondModel
+        {
+            P6 = "1234",
+        };
+
+        ThirdModel? thirdModel = new ThirdModel
+        {
+            Value = 11
+        };
+
+        var firstModel = new FirstModel
+        {
+            P1 = new[] { secondModel },
+            P2 = new[] { secondModel, secondModelB, secondModelC },
+            P51 = new[] { thirdModel }
+        };
+
+        var validator = default(FirstValidator);
+        var vr = validator.Validate("Enumeration", firstModel);
+
+        Utils.VerifyValidateOptionsResult(vr, 4, "P1[0].P6", "P2[0].P6", "P2[2].P6", "P51[0].Value");
+    }
+
+    [Fact]
+    public void NullElement()
+    {
+        var firstModel = new FirstModel
+        {
+            P1 = new[] { (SecondModel)null! },
+        };
+
+        var validator = default(FirstValidator);
+        var vr = validator.Validate("Enumeration", firstModel);
+
+        Utils.VerifyValidateOptionsResult(vr, 1, "P1[0]");
+    }
+
+    [Fact]
+    public void Valid()
+    {
+        var secondModel = new SecondModel
+        {
+            P6 = "12345",
+        };
+
+        var thirdModelA = new ThirdModel
+        {
+            Value = 2
+        };
+
+        var thirdModelB = new ThirdModel
+        {
+            Value = 9
+        };
+
+        var firstModel = new FirstModel
+        {
+            P1 = new[] { secondModel },
+            P2 = new[] { secondModel },
+            P3 = new[] { (SecondModel?)null },
+            P4 = new[] { thirdModelA, thirdModelB },
+            P5 = new ThirdModel?[] { thirdModelA, default },
+            P51 = new ThirdModel?[] { thirdModelB, default }
+        };
+
+        var validator = default(FirstValidator);
+        Assert.Equal(ValidateOptionsResult.Success, validator.Validate("Enumeration", firstModel));
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/FieldTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/FieldTests.cs
new file mode 100644 (file)
index 0000000..3656d4f
--- /dev/null
@@ -0,0 +1,62 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Fields;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Microsoft.Gen.OptionsValidation.Test;
+
+public class FieldTests
+{
+    [Fact]
+    public void Invalid()
+    {
+        var thirdModel = new ThirdModel
+        {
+            P5 = "1234",
+        };
+
+        var secondModel = new SecondModel
+        {
+            P4 = "1234",
+        };
+
+        var firstModel = new FirstModel
+        {
+            P1 = "1234",
+            P2 = secondModel,
+            P3 = thirdModel,
+        };
+
+        var validator = default(FirstValidator);
+        var vr = validator.Validate("Fields", firstModel);
+
+        Utils.VerifyValidateOptionsResult(vr, 3, "P1", "P2.P4", "P3.P5");
+    }
+
+    [Fact]
+    public void Valid()
+    {
+        var thirdModel = new ThirdModel
+        {
+            P5 = "12345",
+            P6 = 1
+        };
+
+        var secondModel = new SecondModel
+        {
+            P4 = "12345",
+        };
+
+        var firstModel = new FirstModel
+        {
+            P1 = "12345",
+            P2 = secondModel,
+            P3 = thirdModel,
+        };
+
+        var validator = default(FirstValidator);
+        Assert.Equal(ValidateOptionsResult.Success, validator.Validate("Fields", firstModel));
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/FunnyStringsTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/FunnyStringsTests.cs
new file mode 100644 (file)
index 0000000..29b62a5
--- /dev/null
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using FunnyStrings;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Microsoft.Gen.OptionsValidation.Test;
+
+public class FunnyStringsTests
+{
+    [Fact]
+    public void Invalid()
+    {
+        var firstModel = new FirstModel
+        {
+            P1 = "XXX",
+        };
+
+        var validator = default(FirstValidator);
+        var vr = validator.Validate("FunnyStrings", firstModel);
+
+        Utils.VerifyValidateOptionsResult(vr, 1, "P1");
+    }
+
+    [Fact]
+    public void Valid()
+    {
+        var firstModel = new FirstModel
+        {
+            P1 = "\"\r\n\\",
+        };
+
+        var validator = default(FirstValidator);
+        Assert.Equal(ValidateOptionsResult.Success, validator.Validate("FunnyStrings", firstModel));
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/GenericsTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/GenericsTests.cs
new file mode 100644 (file)
index 0000000..838fe52
--- /dev/null
@@ -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 Generics;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Microsoft.Gen.OptionsValidation.Test;
+
+public class GenericsTests
+{
+    [Fact]
+    public void Invalid()
+    {
+        var secondModel = new SecondModel
+        {
+            P4 = "1234",
+        };
+
+        var firstModel = new FirstModel<int>
+        {
+            P1 = "1234",
+            P3 = secondModel,
+        };
+
+        var validator = new FirstValidator<int>();
+        var vr = validator.Validate("Generics", firstModel);
+
+        Utils.VerifyValidateOptionsResult(vr, 2, "P1", "P3.P4");
+    }
+
+    [Fact]
+    public void Valid()
+    {
+        var secondModel = new SecondModel
+        {
+            P4 = "12345",
+        };
+
+        var firstModel = new FirstModel<int>
+        {
+            P1 = "12345",
+            P3 = secondModel,
+        };
+
+        var validator = new FirstValidator<int>();
+        Assert.Equal(ValidateOptionsResult.Success, validator.Validate("Generics", firstModel));
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/MultiModelValidatorTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/MultiModelValidatorTests.cs
new file mode 100644 (file)
index 0000000..fff83ce
--- /dev/null
@@ -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 Microsoft.Extensions.Options;
+using MultiModelValidator;
+using Xunit;
+
+namespace Microsoft.Gen.OptionsValidation.Test;
+
+public class MultiModelValidatorTests
+{
+    [Fact]
+    public void Invalid()
+    {
+        var secondModel = new SecondModel
+        {
+            P3 = "1234",
+        };
+
+        var firstModel = new FirstModel
+        {
+            P1 = "1234",
+            P2 = secondModel,
+        };
+
+        var validator = default(MultiValidator);
+        var vr = validator.Validate("MultiModelValidator", firstModel);
+
+        Utils.VerifyValidateOptionsResult(vr, 2, "P1", "P2.P3");
+    }
+
+    [Fact]
+    public void Valid()
+    {
+        var secondModel = new SecondModel
+        {
+            P3 = "12345",
+        };
+
+        var firstModel = new FirstModel
+        {
+            P1 = "12345",
+            P2 = secondModel,
+        };
+
+        var validator = default(MultiValidator);
+        Assert.Equal(ValidateOptionsResult.Success, validator.Validate("MultiModelValidator", firstModel));
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/NestedTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/NestedTests.cs
new file mode 100644 (file)
index 0000000..8a4b043
--- /dev/null
@@ -0,0 +1,67 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if ROSLYN_4_0_OR_GREATER
+
+using Microsoft.Extensions.Options;
+using Nested;
+using Xunit;
+
+namespace Microsoft.Gen.OptionsValidation.Test;
+
+public class NestedTests
+{
+    [Fact]
+    public void Invalid()
+    {
+        var thirdModel = new Container1.ThirdModel
+        {
+            P6 = "1234",
+        };
+
+        var secondModel = new Container1.SecondModel
+        {
+            P5 = "1234",
+        };
+
+        var firstModel = new Container1.FirstModel
+        {
+            P1 = "1234",
+            P2 = secondModel,
+            P3 = thirdModel,
+            P4 = secondModel,
+        };
+
+        var validator = default(Container2.Container3.FirstValidator);
+        var vr = validator.Validate("Nested", firstModel);
+
+        Utils.VerifyValidateOptionsResult(vr, 4, "P1", "P2.P5", "P3.P6", "P4.P5");
+    }
+
+    [Fact]
+    public void Valid()
+    {
+        var thirdModel = new Container1.ThirdModel
+        {
+            P6 = "12345",
+        };
+
+        var secondModel = new Container1.SecondModel
+        {
+            P5 = "12345",
+        };
+
+        var firstModel = new Container1.FirstModel
+        {
+            P1 = "12345",
+            P2 = secondModel,
+            P3 = thirdModel,
+            P4 = secondModel,
+        };
+
+        var validator = default(Container2.Container3.FirstValidator);
+        Assert.Equal(ValidateOptionsResult.Success, validator.Validate("Nested", firstModel));
+    }
+}
+
+#endif
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/NoNamespaceTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/NoNamespaceTests.cs
new file mode 100644 (file)
index 0000000..5530b63
--- /dev/null
@@ -0,0 +1,60 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Microsoft.Gen.OptionsValidation.Test;
+
+public class NoNamespaceTests
+{
+    [Fact]
+    public void Invalid()
+    {
+        var thirdModel = new ThirdModelNoNamespace
+        {
+            P5 = "1234",
+        };
+
+        var secondModel = new SecondModelNoNamespace
+        {
+            P4 = "1234",
+        };
+
+        var firstModel = new FirstModelNoNamespace
+        {
+            P1 = "1234",
+            P2 = secondModel,
+            P3 = thirdModel,
+        };
+
+        var validator = new FirstValidatorNoNamespace();
+        var vr = validator.Validate("NoNamespace", firstModel);
+
+        Utils.VerifyValidateOptionsResult(vr, 3, "P1", "P2.P4", "P3.P5");
+    }
+
+    [Fact]
+    public void Valid()
+    {
+        var thirdModel = new ThirdModelNoNamespace
+        {
+            P5 = "12345",
+        };
+
+        var secondModel = new SecondModelNoNamespace
+        {
+            P4 = "12345",
+        };
+
+        var firstModel = new FirstModelNoNamespace
+        {
+            P1 = "12345",
+            P2 = secondModel,
+            P3 = thirdModel,
+        };
+
+        var validator = new FirstValidatorNoNamespace();
+        Assert.Equal(ValidateOptionsResult.Success, validator.Validate("NoNamespace", firstModel));
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/OptionsValidationTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/OptionsValidationTests.cs
new file mode 100644 (file)
index 0000000..4994101
--- /dev/null
@@ -0,0 +1,442 @@
+// 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.ComponentModel.DataAnnotations;
+using System.Globalization;
+using Microsoft.Extensions.Options;
+using TestClasses.OptionsValidation;
+using Xunit;
+
+namespace Microsoft.Gen.OptionsValidation.Test;
+
+public class OptionsValidationTests
+{
+    [Fact]
+    public void RequiredAttributeValid()
+    {
+        var validModel = new RequiredAttributeModel
+        {
+            Val = "val"
+        };
+
+        var modelValidator = new RequiredAttributeModelValidator();
+        var result = modelValidator.Validate(nameof(validModel), validModel);
+
+        Assert.Equal(ValidateOptionsResult.Success, result);
+    }
+
+    [Fact]
+    public void RequiredAttributeInvalid()
+    {
+        var validModel = new RequiredAttributeModel
+        {
+            Val = null
+        };
+
+        var modelValidator = new RequiredAttributeModelValidator();
+        Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(validModel), validModel), 1);
+    }
+
+    [Fact]
+    public void RegularExpressionAttributeValid()
+    {
+        var validModel = new RegularExpressionAttributeModel
+        {
+            Val = " "
+        };
+
+        var modelValidator = new RegularExpressionAttributeModelValidator();
+        var result = modelValidator.Validate(nameof(validModel), validModel);
+
+        Assert.Equal(ValidateOptionsResult.Success, result);
+    }
+
+    [Fact]
+    public void RegularExpressionAttributeInvalid()
+    {
+        var validModel = new RegularExpressionAttributeModel
+        {
+            Val = "Not Space"
+        };
+
+        var modelValidator = new RegularExpressionAttributeModelValidator();
+        Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(validModel), validModel), 1);
+    }
+
+    [Fact]
+    public void EmailAttributeValid()
+    {
+        var validModel = new EmailAttributeModel
+        {
+            Val = "abc@xyz.com"
+        };
+
+        var modelValidator = new EmailAttributeModelValidator();
+        var result = modelValidator.Validate(nameof(validModel), validModel);
+
+        Assert.Equal(ValidateOptionsResult.Success, result);
+    }
+
+    [Fact]
+    public void EmailAttributeInvalid()
+    {
+        var validModel = new EmailAttributeModel
+        {
+            Val = "Not Email Address"
+        };
+
+        var modelValidator = new EmailAttributeModelValidator();
+        Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(validModel), validModel), 1);
+    }
+
+    [Fact]
+    public void CustomValidationAttributeValid()
+    {
+        var validModel = new CustomValidationAttributeModel
+        {
+            Val = "Pass"
+        };
+
+        var modelValidator = new CustomValidationAttributeModelValidator();
+        var result = modelValidator.Validate(nameof(validModel), validModel);
+
+        Assert.Equal(ValidateOptionsResult.Success, result);
+    }
+
+    [Fact]
+    public void CustomValidationAttributeInvalid()
+    {
+        var validModel = new CustomValidationAttributeModel
+        {
+            Val = "NOT PASS"
+        };
+
+        var modelValidator = new CustomValidationAttributeModelValidator();
+        Assert.Throws<ValidationException>(() => modelValidator.Validate(nameof(validModel), validModel));
+    }
+
+    [Fact]
+    public void DataTypeAttributeValid()
+    {
+        var validModel = new DataTypeAttributeModel
+        {
+            Val = "ABC"
+        };
+
+        var modelValidator = new DataTypeAttributeModelValidator();
+        var result = modelValidator.Validate(nameof(validModel), validModel);
+
+        Assert.Equal(ValidateOptionsResult.Success, result);
+    }
+
+    [Fact]
+    public void RangeAttributeModelIntValid()
+    {
+        var validModel = new RangeAttributeModelInt
+        {
+            Val = 1
+        };
+
+        var modelValidator = new RangeAttributeModelIntValidator();
+        var result = modelValidator.Validate(nameof(validModel), validModel);
+
+        Assert.Equal(ValidateOptionsResult.Success, result);
+    }
+
+    [Fact]
+    public void RangeAttributeModelIntInvalid()
+    {
+        var validModel = new RangeAttributeModelInt
+        {
+            Val = 0
+        };
+
+        var modelValidator = new RangeAttributeModelIntValidator();
+        Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(validModel), validModel), 1);
+    }
+
+    [Fact]
+    public void RangeAttributeModelDoubleValid()
+    {
+        var validModel = new RangeAttributeModelDouble
+        {
+            Val = 0.6
+        };
+
+        var modelValidator = new RangeAttributeModelDoubleValidator();
+        var result = modelValidator.Validate(nameof(validModel), validModel);
+
+        Assert.Equal(ValidateOptionsResult.Success, result);
+    }
+
+    [Fact]
+    public void RangeAttributeModelDoubleInvalid()
+    {
+        var validModel = new RangeAttributeModelDouble
+        {
+            Val = 0.1
+        };
+
+        var modelValidator = new RangeAttributeModelDoubleValidator();
+        Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(validModel), validModel), 1);
+    }
+
+    [Fact]
+    public void RangeAttributeModelDateValid()
+    {
+#if NETCOREAPP3_1_OR_GREATER
+        // Setting non-invariant culture to check that
+        // attribute's "ParseLimitsInInvariantCulture" property
+        // was set up correctly in the validator:
+        CultureInfo.CurrentCulture = new CultureInfo("cs");
+#else
+        // Setting invariant culture to avoid DateTime parsing discrepancies:
+        CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
+#endif
+        var validModel = new RangeAttributeModelDate
+        {
+            Val = new DateTime(day: 3, month: 1, year: 2004)
+        };
+
+        var modelValidator = new RangeAttributeModelDateValidator();
+        var result = modelValidator.Validate(nameof(validModel), validModel);
+
+        Assert.Equal(ValidateOptionsResult.Success, result);
+    }
+
+    [Fact]
+    public void RangeAttributeModelDateInvalid()
+    {
+        var validModel = new RangeAttributeModelDate
+        {
+            Val = new DateTime(day: 1, month: 1, year: 2004)
+        };
+
+        var modelValidator = new RangeAttributeModelDateValidator();
+        Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(validModel), validModel), 1);
+    }
+
+    [Fact]
+    public void MultipleAttributeModelValid()
+    {
+        var validModel = new MultipleAttributeModel
+        {
+            Val1 = "abc",
+            Val2 = 2,
+            Val3 = 4,
+            Val4 = 6
+        };
+
+        var modelValidator = new MultipleAttributeModelValidator();
+        var result = modelValidator.Validate(nameof(validModel), validModel);
+
+        Assert.Equal(ValidateOptionsResult.Success, result);
+    }
+
+    [Theory]
+    [InlineData("", 2, 4, 7)]
+    [InlineData(null, 2, 4, 7)]
+    [InlineData("abc", 0, 4, 9)]
+    [InlineData("abc", 2, 8, 8)]
+    [InlineData("abc", 2, 4, 10)]
+    public void MultipleAttributeModelInvalid(string val1, int val2, int val3, int val4)
+    {
+        var validModel = new MultipleAttributeModel
+        {
+            Val1 = val1,
+            Val2 = val2,
+            Val3 = val3,
+            Val4 = val4
+        };
+
+        var modelValidator = new MultipleAttributeModelValidator();
+        Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(validModel), validModel), 1);
+    }
+
+    [Fact]
+    public void CustomTypeCustomValidationAttributeModelValid()
+    {
+        var validModel = new CustomTypeCustomValidationAttributeModel
+        {
+            Val = new CustomType { Val1 = "Pass", Val2 = "Pass" }
+        };
+
+        var modelValidator = new CustomTypeCustomValidationAttributeModelValidator();
+        var result = modelValidator.Validate(nameof(validModel), validModel);
+
+        Assert.Equal(ValidateOptionsResult.Success, result);
+    }
+
+    [Fact]
+    public void CustomTypeCustomValidationAttributeModelInvalid()
+    {
+        var validModel = new CustomTypeCustomValidationAttributeModel
+        {
+            Val = new CustomType { Val1 = "Pass", Val2 = "Not Pass" }
+        };
+
+        var modelValidator = new CustomTypeCustomValidationAttributeModelValidator();
+        Assert.Throws<ValidationException>(() => modelValidator.Validate(nameof(validModel), validModel));
+    }
+
+    [Fact]
+    public void DerivedModelIsValid()
+    {
+        var validModel = new DerivedModel
+        {
+            Val = 1,
+            DerivedVal = "Valid",
+            VirtualValWithAttr = 1,
+            VirtualValWithoutAttr = null
+        };
+
+        ((RequiredAttributeModel)validModel).Val = "Valid hidden member from base class";
+
+        var validator = new DerivedModelValidator();
+        var result = validator.Validate(nameof(validModel), validModel);
+        Assert.Equal(ValidateOptionsResult.Success, result);
+    }
+
+    [Theory]
+    [InlineData(0, "", 1, null, "Valid hidden member from base class")]
+    [InlineData(null, "Valid", 1, null, "Valid hidden member from base class")]
+    [InlineData(1, "Valid", null, null, "Valid hidden member from base class")]
+    public void DerivedModelIsInvalid(int? val, string? derivedVal, int? virtValAttr, int? virtVal, string? hiddenValBaseClass)
+    {
+        var invalidModel = new DerivedModel
+        {
+            Val = val,
+            DerivedVal = derivedVal,
+            VirtualValWithAttr = virtValAttr,
+            VirtualValWithoutAttr = virtVal
+        };
+
+        ((RequiredAttributeModel)invalidModel).Val = hiddenValBaseClass;
+
+        var validator = new DerivedModelValidator();
+        Utils.VerifyValidateOptionsResult(validator.Validate(nameof(invalidModel), invalidModel), 1);
+    }
+
+    [Fact]
+    public void LeafModelIsValid()
+    {
+        var validModel = new LeafModel
+        {
+            Val = 1,
+            DerivedVal = "Valid",
+            VirtualValWithAttr = null,
+            VirtualValWithoutAttr = 1
+        };
+
+        ((RequiredAttributeModel)validModel).Val = "Valid hidden member from base class";
+
+        var validator = new LeafModelValidator();
+        var result = validator.Validate(nameof(validModel), validModel);
+        Assert.Equal(ValidateOptionsResult.Success, result);
+    }
+
+    [Fact]
+    public void ComplexModelValid()
+    {
+        var validModel = new ComplexModel
+        {
+            ComplexVal = new RequiredAttributeModel { Val = "Valid" }
+        };
+
+        var modelValidator = new ComplexModelValidator();
+        var result = modelValidator.Validate(nameof(validModel), validModel);
+        Assert.Equal(ValidateOptionsResult.Success, result);
+
+        validModel = new ComplexModel
+        {
+            ValWithoutOptionsValidator = new TypeWithoutOptionsValidator
+            {
+                Val1 = "Valid",
+                Val2 = new DateTime(day: 3, month: 1, year: 2004)
+            }
+        };
+
+        // Setting invariant culture to avoid DateTime parsing discrepancies:
+        CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
+        result = modelValidator.Validate(nameof(validModel), validModel);
+        Assert.Equal(ValidateOptionsResult.Success, result);
+
+        validModel = new ComplexModel
+        {
+            ValWithoutOptionsValidator = new TypeWithoutOptionsValidator
+            {
+                Val1 = "A",
+                Val2 = new DateTime(day: 2, month: 2, year: 2004),
+                YetAnotherComplexVal = new RangeAttributeModelDouble { Val = 0.7 }
+            }
+        };
+
+        result = modelValidator.Validate(nameof(validModel), validModel);
+        Assert.Equal(ValidateOptionsResult.Success, result);
+    }
+
+    [Fact]
+    public void ComplexModelInvalid()
+    {
+        var invalidModel = new ComplexModel
+        {
+            ComplexVal = new RequiredAttributeModel { Val = null }
+        };
+
+        var modelValidator = new ComplexModelValidator();
+        Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(invalidModel), invalidModel), 1);
+
+        invalidModel = new ComplexModel
+        {
+            ValWithoutOptionsValidator = new TypeWithoutOptionsValidator { Val1 = "Valid", Val2 = new DateTime(2003, 3, 3) }
+        };
+
+        Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(invalidModel), invalidModel), 1);
+
+        invalidModel = new ComplexModel
+        {
+            ValWithoutOptionsValidator = new TypeWithoutOptionsValidator { Val1 = string.Empty, Val2 = new DateTime(2004, 3, 3) }
+        };
+
+        Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(invalidModel), invalidModel), 1);
+
+        invalidModel = new ComplexModel
+        {
+            ValWithoutOptionsValidator = new TypeWithoutOptionsValidator
+            {
+                Val1 = "A",
+                Val2 = new DateTime(2004, 2, 2),
+                YetAnotherComplexVal = new RangeAttributeModelDouble { Val = 0.4999 }
+            }
+        };
+
+        Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(invalidModel), invalidModel), 1);
+    }
+
+    [Fact]
+    public void AttributePropertyModelTestOnErrorMessage()
+    {
+        var validModel = new AttributePropertyModel
+        {
+            Val1 = 5,
+            Val2 = 1
+        };
+
+        var modelValidator = new AttributePropertyModelValidator();
+        Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(validModel), validModel), 1);
+    }
+
+    [Fact]
+    public void AttributePropertyModelTestOnErrorMessageResource()
+    {
+        var validModel = new AttributePropertyModel
+        {
+            Val1 = 1,
+            Val2 = 5
+        };
+
+        var modelValidator = new AttributePropertyModelValidator();
+        Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(validModel), validModel), 1);
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/RandomMembersTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/RandomMembersTests.cs
new file mode 100644 (file)
index 0000000..1c87892
--- /dev/null
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Extensions.Options;
+using RandomMembers;
+using Xunit;
+
+namespace Microsoft.Gen.OptionsValidation.Test;
+
+public class RandomMembersTests
+{
+    [Fact]
+    public void Invalid()
+    {
+        var firstModel = new FirstModel
+        {
+            P1 = "1234",
+        };
+
+        var validator = new FirstValidator();
+        var vr = validator.Validate("RandomMembers", firstModel);
+
+        Utils.VerifyValidateOptionsResult(vr, 1, "P1");
+    }
+
+    [Fact]
+    public void Valid()
+    {
+        var firstModel = new FirstModel
+        {
+            P1 = "12345",
+        };
+
+        var validator = new FirstValidator();
+        Assert.Equal(ValidateOptionsResult.Success, validator.Validate("RandomMembers", firstModel));
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/RecordTypesTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/RecordTypesTests.cs
new file mode 100644 (file)
index 0000000..bd0d872
--- /dev/null
@@ -0,0 +1,67 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if ROSLYN_4_0_OR_GREATER
+
+using Microsoft.Extensions.Options;
+using RecordTypes;
+using Xunit;
+
+namespace Microsoft.Gen.OptionsValidation.Test;
+
+public class RecordTypesTests
+{
+    [Fact]
+    public void Invalid()
+    {
+        var thirdModel = new ThirdModel
+        {
+            P6 = "1234",
+        };
+
+        var secondModel = new SecondModel
+        {
+            P5 = "1234",
+        };
+
+        var firstModel = new FirstModel
+        {
+            P1 = "1234",
+            P2 = secondModel,
+            P3 = secondModel,
+            P4 = thirdModel,
+        };
+
+        var validator = default(FirstValidator);
+        var vr = validator.Validate("RecordTypes", firstModel);
+
+        Utils.VerifyValidateOptionsResult(vr, 4, "P1", "P2.P5", "P3.P5", "P4.P6");
+    }
+
+    [Fact]
+    public void Valid()
+    {
+        var thirdModel = new ThirdModel
+        {
+            P6 = "12345",
+        };
+
+        var secondModel = new SecondModel
+        {
+            P5 = "12345",
+        };
+
+        var firstModel = new FirstModel
+        {
+            P1 = "12345",
+            P2 = secondModel,
+            P3 = secondModel,
+            P4 = thirdModel,
+        };
+
+        var validator = default(FirstValidator);
+        Assert.Equal(ValidateOptionsResult.Success, validator.Validate("RecordTypes", firstModel));
+    }
+}
+
+#endif
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/RepeatedTypesTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/RepeatedTypesTests.cs
new file mode 100644 (file)
index 0000000..b45fcbf
--- /dev/null
@@ -0,0 +1,61 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Extensions.Options;
+using RepeatedTypes;
+using Xunit;
+
+namespace Microsoft.Gen.OptionsValidation.Test;
+
+public class RepeatedTypesTests
+{
+    [Fact]
+    public void Invalid()
+    {
+        var thirdModel = new ThirdModel
+        {
+            P5 = "1234",
+        };
+
+        var secondModel = new SecondModel
+        {
+            P4 = thirdModel,
+        };
+
+        var firstModel = new FirstModel
+        {
+            P1 = secondModel,
+            P2 = secondModel,
+            P3 = thirdModel,
+        };
+
+        var validator = new FirstValidator();
+        var vr = validator.Validate("RepeatedTypes", firstModel);
+
+        Utils.VerifyValidateOptionsResult(vr, 3, "P1.P4.P5", "P2.P4.P5", "P3.P5");
+    }
+
+    [Fact]
+    public void Valid()
+    {
+        var thirdModel = new ThirdModel
+        {
+            P5 = "12345",
+        };
+
+        var secondModel = new SecondModel
+        {
+            P4 = thirdModel,
+        };
+
+        var firstModel = new FirstModel
+        {
+            P1 = secondModel,
+            P2 = secondModel,
+            P3 = thirdModel,
+        };
+
+        var validator = new FirstValidator();
+        Assert.Equal(ValidateOptionsResult.Success, validator.Validate("RepeatedTypes", firstModel));
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/SelfValidationTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/SelfValidationTests.cs
new file mode 100644 (file)
index 0000000..0a51133
--- /dev/null
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Extensions.Options;
+using SelfValidation;
+using Xunit;
+
+namespace Microsoft.Gen.OptionsValidation.Test;
+
+public class SelfValidationTests
+{
+    [Fact]
+    public void Invalid()
+    {
+        var firstModel = new FirstModel
+        {
+            P1 = "1234",
+        };
+
+        var validator = default(FirstValidator);
+        var vr = validator.Validate("SelfValidation", firstModel);
+
+        Utils.VerifyValidateOptionsResult(vr, 1, "P1");
+    }
+
+    [Fact]
+    public void Valid()
+    {
+        var firstModel = new FirstModel
+        {
+            P1 = "12345",
+        };
+
+        var validator = default(FirstValidator);
+        Assert.Equal(ValidateOptionsResult.Success, validator.Validate("SelfValidation", firstModel));
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/Utils.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/Utils.cs
new file mode 100644 (file)
index 0000000..7412374
--- /dev/null
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if NETCOREAPP3_1_OR_GREATER
+using System.Linq;
+#endif
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Microsoft.Gen.OptionsValidation.Test;
+
+internal static class Utils
+{
+    public static void VerifyValidateOptionsResult(ValidateOptionsResult vr, int expectedErrorCount, params string[] expectedErrorSubstrings)
+    {
+        Assert.NotNull(vr);
+
+#if NETCOREAPP3_1_OR_GREATER
+        var failures = vr.Failures!.ToArray();
+#else
+        var failures = vr.FailureMessage!.Split(';');
+#endif
+
+        Assert.Equal(expectedErrorCount, failures.Length);
+
+        for (int i = 0; i < expectedErrorSubstrings.Length; i++)
+        {
+            Assert.Contains(expectedErrorSubstrings[i], failures[i]);
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/ValueTypesTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/ValueTypesTests.cs
new file mode 100644 (file)
index 0000000..543e8eb
--- /dev/null
@@ -0,0 +1,53 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Extensions.Options;
+using ValueTypes;
+using Xunit;
+
+namespace Microsoft.Gen.OptionsValidation.Test;
+
+public class ValueTypesTests
+{
+    [Fact]
+    public void Invalid()
+    {
+        var secondModel = new SecondModel
+        {
+            P4 = "1234",
+        };
+
+        var firstModel = new FirstModel
+        {
+            P1 = "1234",
+            P3 = secondModel,
+            P2 = secondModel,
+            P4 = default,
+        };
+
+        var validator = default(FirstValidator);
+        var vr = validator.Validate("ValueTypes", firstModel);
+
+        Utils.VerifyValidateOptionsResult(vr, 3, "P1", "P2.P4", "P3.P4");
+    }
+
+    [Fact]
+    public void Valid()
+    {
+        var secondModel = new SecondModel
+        {
+            P4 = "12345",
+        };
+
+        var firstModel = new FirstModel
+        {
+            P1 = "12345",
+            P3 = secondModel,
+            P2 = secondModel,
+            P4 = default,
+        };
+
+        var validator = default(FirstValidator);
+        Assert.Equal(ValidateOptionsResult.Success, validator.Validate("ValueTypes", firstModel));
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Microsoft.Extensions.Options.SourceGeneration.Tests.csproj b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Microsoft.Extensions.Options.SourceGeneration.Tests.csproj
new file mode 100644 (file)
index 0000000..54f7dfd
--- /dev/null
@@ -0,0 +1,55 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFrameworks>$(NetCoreAppCurrent);$(NetFrameworkMinimum)</TargetFrameworks>
+    <RoslynApiVersion>$(MicrosoftCodeAnalysisVersion_4_4)</RoslynApiVersion>
+    <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
+    <!-- <CompilerGeneratedFilesOutputPath>$(OutputPath)/$(TargetFramework)/Generated</CompilerGeneratedFilesOutputPath> -->
+    <EnableDefaultItems>true</EnableDefaultItems>
+    <!-- <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules> -->
+    <DefineConstants>$(DefineConstants);ROSLYN4_0_OR_GREATER;ROSLYN4_4_OR_GREATER;ROSLYN_4_0_OR_GREATER</DefineConstants>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Compile Include="$(CoreLibSharedDir)System\Runtime\CompilerServices\IsExternalInit.cs" Link="Common\System\Runtime\CompilerServices\IsExternalInit.cs" />
+    <Compile Include="$(CommonPath)System\ThrowHelper.cs" Link="Common\System\ThrowHelper.cs" />
+    <Compile Include="$(CommonPath)..\tests\SourceGenerators\RoslynTestUtils.cs" Link="SourceGenerators\RoslynTestUtils.cs" />
+
+    <Compile Include="$(LibrariesProjectRoot)Microsoft.Extensions.Options\gen\DiagDescriptorsBase.cs" Link="gen\DiagDescriptorsBase.cs" />
+    <Compile Include="$(LibrariesProjectRoot)Microsoft.Extensions.Options\gen\DiagDescriptors.cs" Link="gen\DiagDescriptors.cs" />
+    <Compile Remove="Baselines\**\*.cs" />
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETFramework'">
+    <ProjectReference Include="$(LibrariesProjectRoot)System.ComponentModel.Annotations\src\System.ComponentModel.Annotations.csproj" SkipUseReferenceAssembly="true" />
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
+    <Reference Include="System.ComponentModel.DataAnnotations" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.CodeAnalysis" Version="$(MicrosoftCodeAnalysisVersion)" />
+    <ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Options\src\Microsoft.Extensions.Options.csproj" SkipUseReferenceAssembly="true" />
+    <ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Options\gen\Microsoft.Extensions.Options.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="true" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Content Include="TestClasses\**\*">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+
+    <Content Include="Baselines\**\*">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+
+  <Target Name="FixIncrementalCoreCompileWithAnalyzers" BeforeTargets="CoreCompile">
+    <ItemGroup>
+      <CustomAdditionalCompileInputs Include="@(Analyzer)" />
+    </ItemGroup>
+  </Target>
+
+  <!-- <Target Name="MakeMyDir" AfterTargets="PrepareForBuild">
+    <MakeDir Directories="$(OutputPath)/$(TargetFramework)/Generated" Condition="!Exists('$(OutputPath)/$(TargetFramework)/Generated')"  />
+  </Target> -->
+</Project>
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Resources/Strings.resx b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Resources/Strings.resx
new file mode 100644 (file)
index 0000000..1673d46
--- /dev/null
@@ -0,0 +1,201 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!--
+    Microsoft ResX Schema
+
+    Version 2.0
+
+    The primary goals of this format is to allow a simple XML format
+    that is mostly human readable. The generation and parsing of the
+    various data types are done through the TypeConverter classes
+    associated with the data types.
+
+    Example:
+
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+
+    There are any number of "resheader" rows that contain simple
+    name/value pairs.
+
+    Each data row contains a name, and value. The row also contains a
+    type or mimetype. Type corresponds to a .NET class that support
+    text/value conversion through the TypeConverter architecture.
+    Classes that don't support this are serialized and stored with the
+    mimetype set.
+
+    The mimetype is used for serialized objects, and tells the
+    ResXResourceReader how to depersist the object. This is currently not
+    extensible. For a given mimetype the value must be set accordingly:
+
+    Note - application/x-microsoft.net.object.binary.base64 is the format
+    that the ResXResourceWriter will generate, however the reader can
+    read any of the formats listed below.
+
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="ErrorMessageResourceName" xml:space="preserve">
+    <value>ErrorMessageResourceName</value>
+  </data>
+  <data name="CantUseWithGenericTypesTitle" xml:space="preserve">
+    <value>Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types.</value>
+  </data>
+  <data name="PotentiallyMissingEnumerableValidationMessage" xml:space="preserve">
+    <value>Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight.</value>
+  </data>
+  <data name="PotentiallyMissingEnumerableValidationTitle" xml:space="preserve">
+    <value>Member potentially missing enumerable validation.</value>
+  </data>
+  <data name="PotentiallyMissingTransitiveValidationMessage" xml:space="preserve">
+    <value>Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight.</value>
+  </data>
+  <data name="CircularTypeReferencesMessage" xml:space="preserve">
+    <value>There is a circular type reference involving type {0} preventing it from being used for static validation.</value>
+  </data>
+  <data name="CircularTypeReferencesTitle" xml:space="preserve">
+    <value>Unsupported circular references in model types.</value>
+  </data>
+  <data name="MemberIsInaccessibleMessage" xml:space="preserve">
+    <value>Can't apply validation attributes to private field or property {0}.</value>
+  </data>
+  <data name="NotEnumerableTypeMessage" xml:space="preserve">
+    <value>[ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable&lt;T&gt;.</value>
+  </data>
+  <data name="NullValidatorTypeTitle" xml:space="preserve">
+    <value>Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes.</value>
+  </data>
+  <data name="CantUseWithGenericTypesMessage" xml:space="preserve">
+    <value>Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}.</value>
+  </data>
+  <data name="NoEligibleMemberTitle" xml:space="preserve">
+    <value>A member type has no fields or properties to validate.</value>
+  </data>
+  <data name="NoEligibleMembersFromValidatorTitle" xml:space="preserve">
+    <value>A type has no fields or properties to validate.</value>
+  </data>
+  <data name="NotEnumerableTypeTitle" xml:space="preserve">
+    <value>Member type is not enumerable.</value>
+  </data>
+  <data name="ValidatorsNeedSimpleConstructorMessage" xml:space="preserve">
+    <value>Validator type {0} doesn't have a parameterless constructor.</value>
+  </data>
+  <data name="DoesntImplementIValidateOptionsMessage" xml:space="preserve">
+    <value>Type {0} does not implement the required IValidateOptions&lt;{1}&gt; interface.</value>
+  </data>
+  <data name="ValidatorsNeedSimpleConstructorTitle" xml:space="preserve">
+    <value>Validators used for transitive or enumerable validation must have a constructor with no parameters.</value>
+  </data>
+  <data name="NoEligibleMembersFromValidatorMessage" xml:space="preserve">
+    <value>Type {0} has no fields or properties to validate, referenced by type {1}.</value>
+  </data>
+  <data name="NoEligibleMemberMessage" xml:space="preserve">
+    <value>Type {0} has no fields or properties to validate, referenced from member {1}.</value>
+  </data>
+  <data name="DoesntImplementIValidateOptionsTitle" xml:space="preserve">
+    <value>A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface.</value>
+  </data>
+  <data name="MemberIsInaccessibleTitle" xml:space="preserve">
+    <value>Can't validate private fields or properties.</value>
+  </data>
+  <data name="CantBeStaticClassMessage" xml:space="preserve">
+    <value>[OptionsValidator] cannot be applied to static class {0}.</value>
+  </data>
+  <data name="AlreadyImplementsValidateMethodTitle" xml:space="preserve">
+    <value>A type already includes an implementation of the 'Validate' method.</value>
+  </data>
+  <data name="AlreadyImplementsValidateMethodMessage" xml:space="preserve">
+    <value>Type {0} already implements the Validate method.</value>
+  </data>
+  <data name="CantBeStaticClassTitle" xml:space="preserve">
+    <value>'OptionsValidatorAttribute' can't be applied to a static class.</value>
+  </data>
+  <data name="NullValidatorTypeMessage" xml:space="preserve">
+    <value>Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes.</value>
+  </data>
+  <data name="PotentiallyMissingTransitiveValidationTitle" xml:space="preserve">
+    <value>Member potentially missing transitive validation.</value>
+  </data>
+</root>
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/CustomAttr.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/CustomAttr.cs
new file mode 100644 (file)
index 0000000..aa75c2e
--- /dev/null
@@ -0,0 +1,68 @@
+// 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.ComponentModel.DataAnnotations;
+using Microsoft.Extensions.Options;
+
+namespace CustomAttr
+{
+#pragma warning disable SA1649
+#pragma warning disable SA1402
+#pragma warning disable CA1019
+#pragma warning disable IDE0052
+
+    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
+    public sealed class CustomAttribute : ValidationAttribute
+    {
+        private readonly char _ch;
+        private readonly bool _caseSensitive;
+        private readonly string? _extra;
+
+        public CustomAttribute(char ch, bool caseSensitive, string? extra)
+        {
+            _ch = ch;
+            _caseSensitive = caseSensitive;
+            _extra = extra;
+        }
+
+        protected override ValidationResult IsValid(object? value, ValidationContext? validationContext)
+        {
+            if (value == null)
+            {
+                return ValidationResult.Success!;
+            }
+
+            if (_caseSensitive)
+            {
+                if ((char)value != _ch)
+                {
+                    return new ValidationResult($"{validationContext?.MemberName} didn't match");
+                }
+            }
+            else
+            {
+                if (char.ToUpperInvariant((char)value) != char.ToUpperInvariant(_ch))
+                {
+                    return new ValidationResult($"{validationContext?.MemberName} didn't match");
+                }
+            }
+
+            return ValidationResult.Success!;
+        }
+    }
+
+    public class FirstModel
+    {
+        [Custom('A', true, null)]
+        public char P1 { get; set; }
+
+        [Custom('A', false, "X")]
+        public char P2 { get; set; }
+    }
+
+    [OptionsValidator]
+    public partial class FirstValidator : IValidateOptions<FirstModel>
+    {
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Enumeration.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Enumeration.cs
new file mode 100644 (file)
index 0000000..549e5fb
--- /dev/null
@@ -0,0 +1,93 @@
+// 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.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Extensions.Options;
+
+namespace Enumeration
+{
+#pragma warning disable SA1649
+#pragma warning disable SA1402
+
+    public class FirstModel
+    {
+        [ValidateEnumeratedItems]
+        public IList<SecondModel>? P1;
+
+        [ValidateEnumeratedItems(typeof(SecondValidator))]
+        public IList<SecondModel>? P2;
+
+        [ValidateEnumeratedItems]
+        public IList<SecondModel?>? P3;
+
+        [ValidateEnumeratedItems]
+        public IList<ThirdModel>? P4;
+
+        [ValidateEnumeratedItems]
+        public IList<ThirdModel?>? P5;
+
+        [ValidateEnumeratedItems]
+        [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1125:Use shorthand for nullable types", Justification = "Testing System>Nullable<T>")]
+        public IList<Nullable<ThirdModel>>? P51;
+
+        [ValidateEnumeratedItems]
+        public SynteticEnumerable? P6;
+
+        [ValidateEnumeratedItems]
+        public SynteticEnumerable P7;
+
+        [ValidateEnumeratedItems]
+        [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1125:Use shorthand for nullable types", Justification = "Testing System>Nullable<T>")]
+        public Nullable<SynteticEnumerable> P8;
+    }
+
+    public class SecondModel
+    {
+        [Required]
+        [MinLength(5)]
+        public string P6 = string.Empty;
+    }
+
+    public struct ThirdModel
+    {
+        [Range(0, 10)]
+        public int Value;
+    }
+
+    public struct SynteticEnumerable : IEnumerable<SecondModel>
+    {
+        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+        public IEnumerator<SecondModel> GetEnumerator() => new InternalEnumerator();
+
+        private class InternalEnumerator : IEnumerator<SecondModel>
+        {
+            public SecondModel Current => throw new NotSupportedException();
+
+            object IEnumerator.Current => Current;
+
+            public void Dispose()
+            {
+                // Nothing to dispose...
+            }
+
+            public bool MoveNext() => false;
+
+            public void Reset() => throw new NotSupportedException();
+        }
+    }
+
+    [OptionsValidator]
+    public partial struct FirstValidator : IValidateOptions<FirstModel>
+    {
+    }
+
+    [OptionsValidator]
+    public partial struct SecondValidator : IValidateOptions<SecondModel>
+    {
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Fields.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Fields.cs
new file mode 100644 (file)
index 0000000..0a6ab14
--- /dev/null
@@ -0,0 +1,71 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.ComponentModel.DataAnnotations;
+using Microsoft.Extensions.Options;
+
+namespace Fields
+{
+#pragma warning disable SA1649
+#pragma warning disable SA1402
+#pragma warning disable S1186
+#pragma warning disable CA1822
+
+    public class FirstModel
+    {
+        [Required]
+        [MinLength(5)]
+        public string P1 = string.Empty;
+
+        [Microsoft.Extensions.Options.ValidateObjectMembers(typeof(SecondValidator))]
+        public SecondModel? P2;
+
+        [Microsoft.Extensions.Options.ValidateObjectMembers]
+        public ThirdModel P3;
+    }
+
+    public class SecondModel
+    {
+        [Required]
+        [MinLength(5)]
+        public string P4 = string.Empty;
+    }
+
+    public struct ThirdModel
+    {
+        [Required]
+        [MinLength(5)]
+        public string P5 = string.Empty;
+
+        public int P6 = default;
+
+        public ThirdModel(object _)
+        {
+        }
+    }
+
+    [OptionsValidator]
+    public partial struct FirstValidator : IValidateOptions<FirstModel>
+    {
+        public void Validate()
+        {
+        }
+
+        public void Validate(int _)
+        {
+        }
+
+        public void Validate(string? _)
+        {
+        }
+
+        public void Validate(string? _0, object _1)
+        {
+        }
+    }
+
+    [OptionsValidator]
+    public partial struct SecondValidator : IValidateOptions<SecondModel>
+    {
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/FileScopedNamespace.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/FileScopedNamespace.cs
new file mode 100644 (file)
index 0000000..cf76ea3
--- /dev/null
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.ComponentModel.DataAnnotations;
+using Microsoft.Extensions.Options;
+
+namespace FileScopedNamespace;
+
+#pragma warning disable SA1649 // File name should match first type name
+
+public class FirstModel
+{
+    [Required]
+    [MinLength(5)]
+    public string P1 = string.Empty;
+}
+
+[OptionsValidator]
+public partial struct FirstValidator : IValidateOptions<FirstModel>
+{
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/FunnyStrings.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/FunnyStrings.cs
new file mode 100644 (file)
index 0000000..c1c874a
--- /dev/null
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.ComponentModel.DataAnnotations;
+using Microsoft.Extensions.Options;
+
+namespace FunnyStrings
+{
+#pragma warning disable SA1649
+#pragma warning disable SA1402
+
+    public class FirstModel
+    {
+        [RegularExpression("\"\r\n\\\\")]
+        public string P1 { get; set; } = string.Empty;
+    }
+
+    [OptionsValidator]
+    public partial struct FirstValidator : IValidateOptions<FirstModel>
+    {
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Generics.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Generics.cs
new file mode 100644 (file)
index 0000000..51a59a7
--- /dev/null
@@ -0,0 +1,35 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.ComponentModel.DataAnnotations;
+using Microsoft.Extensions.Options;
+
+namespace Generics
+{
+#pragma warning disable SA1649
+#pragma warning disable SA1402
+
+    public class FirstModel<T>
+    {
+        [Required]
+        [MinLength(5)]
+        public string P1 { get; set; } = string.Empty;
+
+        public T? P2 { get; set; }
+
+        [Microsoft.Extensions.Options.ValidateObjectMembers]
+        public SecondModel? P3 { get; set; }
+    }
+
+    public class SecondModel
+    {
+        [Required]
+        [MinLength(5)]
+        public string P4 { get; set; } = string.Empty;
+    }
+
+    [OptionsValidator]
+    public partial class FirstValidator<T> : IValidateOptions<FirstModel<T>>
+    {
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Models.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Models.cs
new file mode 100644 (file)
index 0000000..31993d0
--- /dev/null
@@ -0,0 +1,251 @@
+// 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.ComponentModel.DataAnnotations;
+using Microsoft.Extensions.Options;
+using Microsoft.Gen.OptionsValidation.Test;
+
+#pragma warning disable SA1649
+#pragma warning disable SA1402
+
+namespace TestClasses.OptionsValidation
+{
+    // ValidationAttribute without parameter
+    public class RequiredAttributeModel
+    {
+        [Required]
+        public string? Val { get; set; }
+    }
+
+    // ValidationAttribute with string parameter
+    public class RegularExpressionAttributeModel
+    {
+        [RegularExpression("\\s")]
+        public string Val { get; set; } = string.Empty;
+    }
+
+    // DataTypeAttribute
+    public class EmailAttributeModel
+    {
+        [EmailAddress]
+        public string Val { get; set; } = string.Empty;
+    }
+
+    // ValidationAttribute with System.Type parameter
+    public class CustomValidationAttributeModel
+    {
+        [CustomValidation(typeof(CustomValidationTest), "TestMethod")]
+        public string Val { get; set; } = string.Empty;
+    }
+
+#pragma warning disable SA1204 // Static elements should appear before instance elements
+    public static class CustomValidationTest
+#pragma warning restore SA1204 // Static elements should appear before instance elements
+    {
+        public static ValidationResult? TestMethod(string val, ValidationContext _)
+        {
+            if (val.Equals("Pass", StringComparison.Ordinal))
+            {
+                return ValidationResult.Success;
+            }
+
+            throw new ValidationException();
+        }
+    }
+
+    // ValidationAttribute with DataType parameter
+    public class DataTypeAttributeModel
+    {
+        [DataType(DataType.Text)]
+        public string Val { get; set; } = string.Empty;
+    }
+
+    // ValidationAttribute with type, double, int parameters
+    public class RangeAttributeModelInt
+    {
+        [Range(1, 3)]
+        public int Val { get; set; }
+    }
+
+    public class RangeAttributeModelDouble
+    {
+        [Range(0.5, 0.9)]
+        public double Val { get; set; }
+    }
+
+    public class RangeAttributeModelDate
+    {
+#if NETCOREAPP3_1_OR_GREATER
+        [Range(typeof(DateTime), "1/2/2004", "3/4/2004", ParseLimitsInInvariantCulture = true)]
+#else
+        [Range(typeof(DateTime), "1/2/2004", "3/4/2004")]
+#endif
+        public DateTime Val { get; set; }
+    }
+
+    public class MultipleAttributeModel
+    {
+        [Required]
+        [DataType(DataType.Password)]
+        public string Val1 { get; set; } = string.Empty;
+
+        [Range(1, 3)]
+        public int Val2 { get; set; }
+
+        [Range(3, 5)]
+        public int Val3 { get; set; }
+
+        [Range(5, 9)]
+        public int Val4 { get; set; }
+    }
+
+    public class CustomTypeCustomValidationAttributeModel
+    {
+        [CustomValidation(typeof(CustomTypeCustomValidationTest), "TestMethod")]
+        public CustomType? Val { get; set; }
+    }
+
+    public class CustomType
+    {
+        public string Val1 { get; set; } = string.Empty;
+        public string Val2 { get; set; } = string.Empty;
+    }
+
+#pragma warning disable SA1204 // Static elements should appear before instance elements
+    public static class CustomTypeCustomValidationTest
+#pragma warning restore SA1204 // Static elements should appear before instance elements
+    {
+        public static ValidationResult? TestMethod(CustomType val, ValidationContext _)
+        {
+            if (val.Val1.Equals("Pass", StringComparison.Ordinal) && val.Val2.Equals("Pass", StringComparison.Ordinal))
+            {
+                return ValidationResult.Success;
+            }
+
+            throw new ValidationException();
+        }
+    }
+
+    public class AttributePropertyModel
+    {
+        [Range(1, 3, ErrorMessage = "ErrorMessage")]
+        public int Val1 { get; set; }
+
+        [Range(1, 3, ErrorMessageResourceType = typeof(SR), ErrorMessageResourceName = "ErrorMessageResourceName")]
+        public int Val2 { get; set; }
+    }
+
+    public class TypeWithoutOptionsValidator
+    {
+        [Required]
+        public string? Val1 { get; set; }
+
+        [Range(typeof(DateTime), "1/2/2004", "3/4/2004")]
+        public DateTime Val2 { get; set; }
+
+        [Microsoft.Extensions.Options.ValidateObjectMembers]
+        public RangeAttributeModelDouble? YetAnotherComplexVal { get; set; }
+    }
+
+    public class DerivedModel : RequiredAttributeModel
+    {
+        [Required]
+        public string? DerivedVal { get; set; }
+
+        [Required]
+        internal virtual int? VirtualValWithAttr { get; set; }
+
+        public virtual int? VirtualValWithoutAttr { get; set; }
+
+        [Required]
+        public new int? Val { get; set; }
+    }
+
+    public class LeafModel : DerivedModel
+    {
+        internal override int? VirtualValWithAttr { get; set; }
+
+        [Required]
+        public override int? VirtualValWithoutAttr { get; set; }
+    }
+
+    public class ComplexModel
+    {
+        [Microsoft.Extensions.Options.ValidateObjectMembers]
+        public RequiredAttributeModel? ComplexVal { get; set; }
+
+        [Microsoft.Extensions.Options.ValidateObjectMembers]
+        public TypeWithoutOptionsValidator? ValWithoutOptionsValidator { get; set; }
+    }
+
+    [OptionsValidator]
+    public partial class RequiredAttributeModelValidator : IValidateOptions<RequiredAttributeModel>
+    {
+    }
+
+    [OptionsValidator]
+    public partial class RegularExpressionAttributeModelValidator : IValidateOptions<RegularExpressionAttributeModel>
+    {
+    }
+
+    [OptionsValidator]
+    public partial class EmailAttributeModelValidator : IValidateOptions<EmailAttributeModel>
+    {
+    }
+
+    [OptionsValidator]
+    public partial class CustomValidationAttributeModelValidator : IValidateOptions<CustomValidationAttributeModel>
+    {
+    }
+
+    [OptionsValidator]
+    public partial class DataTypeAttributeModelValidator : IValidateOptions<DataTypeAttributeModel>
+    {
+    }
+
+    [OptionsValidator]
+    public partial class RangeAttributeModelIntValidator : IValidateOptions<RangeAttributeModelInt>
+    {
+    }
+
+    [OptionsValidator]
+    public partial class RangeAttributeModelDoubleValidator : IValidateOptions<RangeAttributeModelDouble>
+    {
+    }
+
+    [OptionsValidator]
+    public partial class RangeAttributeModelDateValidator : IValidateOptions<RangeAttributeModelDate>
+    {
+    }
+
+    [OptionsValidator]
+    public partial class MultipleAttributeModelValidator : IValidateOptions<MultipleAttributeModel>
+    {
+    }
+
+    [OptionsValidator]
+    public partial class CustomTypeCustomValidationAttributeModelValidator : IValidateOptions<CustomTypeCustomValidationAttributeModel>
+    {
+    }
+
+    [OptionsValidator]
+    public partial class AttributePropertyModelValidator : IValidateOptions<AttributePropertyModel>
+    {
+    }
+
+    [OptionsValidator]
+    public partial class DerivedModelValidator : IValidateOptions<DerivedModel>
+    {
+    }
+
+    [OptionsValidator]
+    public partial class LeafModelValidator : IValidateOptions<LeafModel>
+    {
+    }
+
+    [OptionsValidator]
+    internal sealed partial class ComplexModelValidator : IValidateOptions<ComplexModel>
+    {
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/MultiModelValidator.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/MultiModelValidator.cs
new file mode 100644 (file)
index 0000000..2ca6c78
--- /dev/null
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.ComponentModel.DataAnnotations;
+using Microsoft.Extensions.Options;
+
+namespace MultiModelValidator
+{
+#pragma warning disable SA1649
+#pragma warning disable SA1402
+
+    public class FirstModel
+    {
+        [Required]
+        [MinLength(5)]
+        public string P1 = string.Empty;
+
+        [Microsoft.Extensions.Options.ValidateObjectMembers(typeof(MultiValidator))]
+        public SecondModel? P2;
+    }
+
+    public class SecondModel
+    {
+        [Required]
+        [MinLength(5)]
+        public string P3 = string.Empty;
+    }
+
+    [OptionsValidator]
+    public partial struct MultiValidator : IValidateOptions<FirstModel>, IValidateOptions<SecondModel>
+    {
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Nested.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Nested.cs
new file mode 100644 (file)
index 0000000..262f2fe
--- /dev/null
@@ -0,0 +1,106 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// #if ROSLYN_4_0_OR_GREATER
+// #if ROSLYN4_0_OR_GREATER
+
+using System.ComponentModel.DataAnnotations;
+using Microsoft.Extensions.Options;
+
+namespace Nested
+{
+#pragma warning disable SA1649
+#pragma warning disable SA1402
+
+    public static class Container1
+    {
+        public class FirstModel
+        {
+            [Required]
+            [MinLength(5)]
+            public string P1 { get; set; } = string.Empty;
+
+            [Microsoft.Extensions.Options.ValidateObjectMembers(typeof(Container2.Container3.SecondValidator))]
+            public SecondModel? P2 { get; set; }
+
+            [Microsoft.Extensions.Options.ValidateObjectMembers]
+            public ThirdModel P3 { get; set; }
+
+            [Microsoft.Extensions.Options.ValidateObjectMembers(typeof(Container4.Container5.ThirdValidator))]
+            public SecondModel? P4 { get; set; }
+        }
+
+        public class SecondModel
+        {
+            [Required]
+            [MinLength(5)]
+            public string P5 { get; set; } = string.Empty;
+        }
+
+        public struct ThirdModel
+        {
+            public ThirdModel(int _)
+            {
+            }
+
+            [Required]
+            [MinLength(5)]
+            public string P6 { get; set; } = string.Empty;
+        }
+    }
+
+    public static partial class Container2
+    {
+        public partial class Container3
+        {
+            public Container3(int _)
+            {
+                // nothing to do
+            }
+
+            [OptionsValidator]
+            public partial struct FirstValidator : IValidateOptions<Container1.FirstModel>
+            {
+            }
+
+            [OptionsValidator]
+            public partial struct SecondValidator : IValidateOptions<Container1.SecondModel>
+            {
+            }
+        }
+    }
+
+    public partial record class Container4
+    {
+        public partial record class Container5
+        {
+            public Container5(int _)
+            {
+                // nothing to do
+            }
+
+            [OptionsValidator]
+            public partial struct ThirdValidator : IValidateOptions<Container1.SecondModel>
+            {
+            }
+        }
+    }
+
+    public partial struct Container6
+    {
+        [OptionsValidator]
+        public partial struct FourthValidator : IValidateOptions<Container1.SecondModel>
+        {
+        }
+    }
+
+    public partial record struct Container7
+    {
+        [OptionsValidator]
+        public partial record struct FifthValidator : IValidateOptions<Container1.SecondModel>
+        {
+        }
+    }
+}
+
+// #endif
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/NoNamespace.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/NoNamespace.cs
new file mode 100644 (file)
index 0000000..1eadac9
--- /dev/null
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.ComponentModel.DataAnnotations;
+using Microsoft.Extensions.Options;
+
+#pragma warning disable SA1649
+#pragma warning disable SA1402
+
+public class FirstModelNoNamespace
+{
+    [Required]
+    [MinLength(5)]
+    public string P1 { get; set; } = string.Empty;
+
+    [Microsoft.Extensions.Options.ValidateObjectMembers(typeof(SecondValidatorNoNamespace))]
+    public SecondModelNoNamespace? P2 { get; set; }
+
+    [Microsoft.Extensions.Options.ValidateObjectMembers]
+    public ThirdModelNoNamespace? P3 { get; set; }
+}
+
+public class SecondModelNoNamespace
+{
+    [Required]
+    [MinLength(5)]
+    public string P4 { get; set; } = string.Empty;
+}
+
+public class ThirdModelNoNamespace
+{
+    [Required]
+    [MinLength(5)]
+    public string P5 { get; set; } = string.Empty;
+}
+
+[OptionsValidator]
+public partial class FirstValidatorNoNamespace : IValidateOptions<FirstModelNoNamespace>
+{
+}
+
+[OptionsValidator]
+public partial class SecondValidatorNoNamespace : IValidateOptions<SecondModelNoNamespace>
+{
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RandomMembers.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RandomMembers.cs
new file mode 100644 (file)
index 0000000..b3c5331
--- /dev/null
@@ -0,0 +1,34 @@
+// 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.ComponentModel.DataAnnotations;
+using Microsoft.Extensions.Options;
+
+namespace RandomMembers
+{
+#pragma warning disable SA1649
+#pragma warning disable SA1402
+#pragma warning disable CA1822
+
+    public class FirstModel
+    {
+        [Required]
+        [MinLength(5)]
+        public string? P1 { get; set; }
+
+        public void Foo()
+        {
+            throw new NotSupportedException();
+        }
+
+        public class Nested
+        {
+        }
+    }
+
+    [OptionsValidator]
+    public partial class FirstValidator : IValidateOptions<FirstModel>
+    {
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RecordTypes.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RecordTypes.cs
new file mode 100644 (file)
index 0000000..4d964ce
--- /dev/null
@@ -0,0 +1,67 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// #if ROSLYN_4_0_OR_GREATER
+
+using System.ComponentModel.DataAnnotations;
+using Microsoft.Extensions.Options;
+
+namespace RecordTypes
+{
+#pragma warning disable SA1649
+
+    public record class FirstModel
+    {
+        [Required]
+        [MinLength(5)]
+        public string P1 { get; set; } = string.Empty;
+
+        [Microsoft.Extensions.Options.ValidateObjectMembers(typeof(SecondValidator))]
+        public SecondModel? P2 { get; set; }
+
+        [Microsoft.Extensions.Options.ValidateObjectMembers(typeof(ThirdValidator))]
+        public SecondModel P3 { get; set; } = new SecondModel();
+
+        [Microsoft.Extensions.Options.ValidateObjectMembers]
+        public ThirdModel P4 { get; set; }
+    }
+
+    public record class SecondModel
+    {
+        [Required]
+        [MinLength(5)]
+        public string P5 { get; set; } = string.Empty;
+    }
+
+    public record struct ThirdModel
+    {
+        [Required]
+        [MinLength(5)]
+        public string P6 { get; set; } = string.Empty;
+
+        public ThirdModel(int _)
+        {
+        }
+
+        public ThirdModel(object _)
+        {
+        }
+    }
+
+    [OptionsValidator]
+    public partial record struct FirstValidator : IValidateOptions<FirstModel>
+    {
+    }
+
+    [OptionsValidator]
+    public partial record struct SecondValidator : IValidateOptions<SecondModel>
+    {
+    }
+
+    [OptionsValidator]
+    public partial record class ThirdValidator : IValidateOptions<SecondModel>
+    {
+    }
+}
+
+// #endif
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RepeatedTypes.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RepeatedTypes.cs
new file mode 100644 (file)
index 0000000..db295a4
--- /dev/null
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.ComponentModel.DataAnnotations;
+using Microsoft.Extensions.Options;
+
+namespace RepeatedTypes
+{
+#pragma warning disable SA1649
+#pragma warning disable SA1402
+#pragma warning disable CA1019
+
+    public class FirstModel
+    {
+        [Required]
+        [Microsoft.Extensions.Options.ValidateObjectMembers]
+        public SecondModel? P1 { get; set; }
+
+        [Required]
+        [Microsoft.Extensions.Options.ValidateObjectMembers]
+        public SecondModel? P2 { get; set; }
+
+        [Required]
+        [Microsoft.Extensions.Options.ValidateObjectMembers]
+        public ThirdModel? P3 { get; set; }
+    }
+
+    public class SecondModel
+    {
+        [Required]
+        [Microsoft.Extensions.Options.ValidateObjectMembers]
+        public ThirdModel? P4 { get; set; }
+    }
+
+    public class ThirdModel
+    {
+        [Required]
+        [MinLength(5)]
+        public string? P5;
+    }
+
+    [OptionsValidator]
+    public partial class FirstValidator : IValidateOptions<FirstModel>
+    {
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/SelfValidation.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/SelfValidation.cs
new file mode 100644 (file)
index 0000000..8fe5a9a
--- /dev/null
@@ -0,0 +1,33 @@
+// 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.ComponentModel.DataAnnotations;
+using Microsoft.Extensions.Options;
+
+namespace SelfValidation
+{
+#pragma warning disable SA1649
+
+    public class FirstModel : IValidatableObject
+    {
+        [Required]
+        public string P1 = string.Empty;
+
+        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
+        {
+            if (P1.Length < 5)
+            {
+                return new[] { new ValidationResult("P1 is not long enough") };
+            }
+
+            return Array.Empty<ValidationResult>();
+        }
+    }
+
+    [OptionsValidator]
+    public partial struct FirstValidator : IValidateOptions<FirstModel>
+    {
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/ValueTypes.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/ValueTypes.cs
new file mode 100644 (file)
index 0000000..1cdee37
--- /dev/null
@@ -0,0 +1,45 @@
+// 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.ComponentModel.DataAnnotations;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Extensions.Options;
+
+namespace ValueTypes
+{
+#pragma warning disable SA1649
+
+    public class FirstModel
+    {
+        [Required]
+        [MinLength(5)]
+        public string P1 { get; set; } = string.Empty;
+
+        [ValidateObjectMembers]
+        public SecondModel? P2 { get; set; }
+
+        [ValidateObjectMembers]
+        public SecondModel P3 { get; set; }
+
+        [ValidateObjectMembers]
+        [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1125:Use shorthand for nullable types", Justification = "Testing System>Nullable<T>")]
+        public Nullable<SecondModel> P4 { get; set; }
+    }
+
+    public struct SecondModel
+    {
+        [Required]
+        [MinLength(5)]
+        public string P4 { get; set; } = string.Empty;
+
+        public SecondModel(object _)
+        {
+        }
+    }
+
+    [OptionsValidator]
+    public partial struct FirstValidator : IValidateOptions<FirstModel>
+    {
+    }
+}