From cdca2577c982fa7c45eb150ee2e2b919d24c5b44 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Mon, 4 Aug 2025 23:44:32 +0200 Subject: [PATCH 01/12] Initial implementation --- .../ValidationsGenerator.AttributeParser.cs | 90 +++++++++- src/Validation/gen/ValidationsGenerator.cs | 43 ++++- ...dationsGenerator.AutoGeneratedAttribute.cs | 44 +++++ ...eAttribute#EmbeddedAttribute.g.verified.cs | 9 + ...bute#ValidatableInfoResolver.g.verified.cs | 170 ++++++++++++++++++ ...ute#ValidatableTypeAttribute.g.verified.cs | 7 + ...hAttribute#EmbeddedAttribute.g.verified.cs | 9 + ...ute#ValidatableTypeAttribute.g.verified.cs | 7 + ...mplexTypes#EmbeddedAttribute.g.verified.cs | 9 + ...pes#ValidatableTypeAttribute.g.verified.cs | 7 + ...ableObject#EmbeddedAttribute.g.verified.cs | 9 + ...ect#ValidatableTypeAttribute.g.verified.cs | 7 + ...Namespaces#EmbeddedAttribute.g.verified.cs | 9 + ...ces#ValidatableTypeAttribute.g.verified.cs | 7 + ...Parameters#EmbeddedAttribute.g.verified.cs | 9 + ...ers#ValidatableTypeAttribute.g.verified.cs | 7 + ...rphicTypes#EmbeddedAttribute.g.verified.cs | 9 + ...pes#ValidatableTypeAttribute.g.verified.cs | 7 + ...ecordTypes#EmbeddedAttribute.g.verified.cs | 9 + ...pes#ValidatableTypeAttribute.g.verified.cs | 7 + ...hAttribute#EmbeddedAttribute.g.verified.cs | 9 + ...ute#ValidatableTypeAttribute.g.verified.cs | 7 + ...rsiveTypes#EmbeddedAttribute.g.verified.cs | 9 + ...pes#ValidatableTypeAttribute.g.verified.cs | 7 + ...Properties#EmbeddedAttribute.g.verified.cs | 9 + ...ies#ValidatableTypeAttribute.g.verified.cs | 7 + ...xemptTypes#EmbeddedAttribute.g.verified.cs | 9 + ...pes#ValidatableTypeAttribute.g.verified.cs | 7 + ...CallExists#EmbeddedAttribute.g.verified.cs | 9 + ...sts#ValidatableTypeAttribute.g.verified.cs | 7 + ...CallExists#EmbeddedAttribute.g.verified.cs | 9 + ...sts#ValidatableTypeAttribute.g.verified.cs | 7 + 32 files changed, 564 insertions(+), 7 deletions(-) create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#EmbeddedAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableInfoResolver.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableTypeAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateClassTypesWithAttribute#EmbeddedAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateClassTypesWithAttribute#ValidatableTypeAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypes#EmbeddedAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypes#ValidatableTypeAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateIValidatableObject#EmbeddedAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateIValidatableObject#ValidatableTypeAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateMultipleNamespaces#EmbeddedAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateMultipleNamespaces#ValidatableTypeAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateParameters#EmbeddedAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateParameters#ValidatableTypeAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidatePolymorphicTypes#EmbeddedAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidatePolymorphicTypes#ValidatableTypeAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypes#EmbeddedAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypes#ValidatableTypeAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypesWithAttribute#EmbeddedAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypesWithAttribute#ValidatableTypeAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecursiveTypes#EmbeddedAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecursiveTypes#ValidatableTypeAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#EmbeddedAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#ValidatableTypeAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitForExemptTypes#EmbeddedAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitForExemptTypes#ValidatableTypeAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNoAddValidationCallExists#EmbeddedAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNoAddValidationCallExists#ValidatableTypeAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNotCorrectAddValidationCallExists#EmbeddedAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNotCorrectAddValidationCallExists#ValidatableTypeAttribute.g.verified.cs diff --git a/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs b/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs index 6de21bac7d81..6322e90da72a 100644 --- a/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs +++ b/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs @@ -1,8 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Threading; using Microsoft.AspNetCore.App.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; @@ -12,12 +14,12 @@ namespace Microsoft.Extensions.Validation; public sealed partial class ValidationsGenerator : IIncrementalGenerator { - internal static bool ShouldTransformSymbolWithAttribute(SyntaxNode syntaxNode, CancellationToken cancellationToken) + internal static bool ShouldTransformSymbolWithAttribute(SyntaxNode syntaxNode, CancellationToken _) { return syntaxNode is ClassDeclarationSyntax or RecordDeclarationSyntax; } - internal ImmutableArray TransformValidatableTypeWithAttribute(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) + internal ImmutableArray TransformValidatableTypeWithAttribute(GeneratorAttributeSyntaxContext context, CancellationToken _) { var validatableTypes = new HashSet(ValidatableTypeComparer.Instance); List visitedTypes = []; @@ -28,4 +30,88 @@ internal ImmutableArray TransformValidatableTypeWithAttribute(G } return []; } + + internal static bool ShouldTransformSymbolWithValidatableTypeAttribute(SyntaxNode syntaxNode, CancellationToken _) + { + // Only process class and record declarations + if (syntaxNode is not (ClassDeclarationSyntax or RecordDeclarationSyntax)) + { + return false; + } + + // Check if the type has any attribute that could be ValidatableTypeAttribute + var typeDeclaration = (TypeDeclarationSyntax)syntaxNode; + return typeDeclaration.AttributeLists + .SelectMany(al => al.Attributes) + .Any(IsValidatableTypeAttribute); + } + + internal ImmutableArray TransformValidatableTypeWithValidatableTypeAttribute(GeneratorSyntaxContext context, CancellationToken cancellationToken) + { + if (context.Node is not (ClassDeclarationSyntax or RecordDeclarationSyntax)) + { + return []; + } + + var typeSymbol = context.SemanticModel.GetDeclaredSymbol(context.Node, cancellationToken) as ITypeSymbol; + if (typeSymbol == null) + { + return []; + } + + // Check if the type has a ValidatableTypeAttribute (framework or auto-generated) + if (!HasValidatableTypeAttribute(typeSymbol)) + { + return []; + } + + var validatableTypes = new HashSet(ValidatableTypeComparer.Instance); + List visitedTypes = []; + var wellKnownTypes = WellKnownTypes.GetOrCreate(context.SemanticModel.Compilation); + + if (TryExtractValidatableType(typeSymbol, wellKnownTypes, ref validatableTypes, ref visitedTypes)) + { + return [..validatableTypes]; + } + return []; + } + + private static bool IsValidatableTypeAttribute(AttributeSyntax attribute) + { + var name = attribute.Name.ToString(); + return name is "ValidatableType" or "ValidatableTypeAttribute" || + name.EndsWith(".ValidatableType", StringComparison.Ordinal) || + name.EndsWith(".ValidatableTypeAttribute", StringComparison.Ordinal); + } + + private static bool HasValidatableTypeAttribute(ITypeSymbol typeSymbol) + { + return typeSymbol.GetAttributes().Any(attr => + { + var attributeClass = attr.AttributeClass; + if (attributeClass == null) + { + return false; + } + + var name = attributeClass.Name; + var fullName = attributeClass.ToDisplayString(); + + // Check for framework attribute + if (fullName == "Microsoft.Extensions.Validation.ValidatableTypeAttribute") + { + return true; + } + + // Check for auto-generated attribute (any namespace) + if (name == "ValidatableTypeAttribute") + { + // Additional check: ensure it's marked with [Embedded] to confirm it's auto-generated + return attributeClass.GetAttributes().Any(embeddedAttr => + embeddedAttr.AttributeClass?.ToDisplayString() == "Microsoft.CodeAnalysis.EmbeddedAttribute"); + } + + return false; + }); + } } diff --git a/src/Validation/gen/ValidationsGenerator.cs b/src/Validation/gen/ValidationsGenerator.cs index 5f7c2c878ac5..bac7b31f9a4e 100644 --- a/src/Validation/gen/ValidationsGenerator.cs +++ b/src/Validation/gen/ValidationsGenerator.cs @@ -11,17 +11,20 @@ public sealed partial class ValidationsGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { + // Emit the ValidatableTypeAttribute with embedded support + context.RegisterPostInitializationOutput(EmitValidatableTypeAttribute); + // Find the builder.Services.AddValidation() call in the application. var addValidation = context.SyntaxProvider.CreateSyntaxProvider( predicate: FindAddValidation, transform: TransformAddValidation ); // Extract types that have been marked with [ValidatableType]. - var validatableTypesWithAttribute = context.SyntaxProvider.ForAttributeWithMetadataName( - "Microsoft.Extensions.Validation.ValidatableTypeAttribute", - predicate: ShouldTransformSymbolWithAttribute, - transform: TransformValidatableTypeWithAttribute - ); + // This handles both the framework attribute and the auto-generated attribute. + var validatableTypesWithAttribute = context.SyntaxProvider.CreateSyntaxProvider( + predicate: ShouldTransformSymbolWithValidatableTypeAttribute, + transform: TransformValidatableTypeWithValidatableTypeAttribute + ).Where(types => types.Length > 0); // Extract all minimal API endpoints in the application. var endpoints = context.SyntaxProvider .CreateSyntaxProvider( @@ -44,4 +47,34 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // ValidatableTypeInfo for all validatable types. context.RegisterSourceOutput(emitInputs, Emit); } + + private static void EmitValidatableTypeAttribute(IncrementalGeneratorPostInitializationContext context) + { + // First, emit the EmbeddedAttribute if it doesn't exist + var embeddedAttributeSource = """ + // + namespace Microsoft.CodeAnalysis + { + [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class EmbeddedAttribute : global::System.Attribute + { + } + } + """; + + context.AddSource("EmbeddedAttribute.g.cs", embeddedAttributeSource); + + // Then emit the ValidatableTypeAttribute in the global namespace + // We use global namespace since we don't have access to the project's root namespace here + var validatableTypeAttributeSource = """ + // + [global::Microsoft.CodeAnalysis.EmbeddedAttribute] + [global::System.AttributeUsage(global::System.AttributeTargets.Class)] + internal sealed class ValidatableTypeAttribute : global::System.Attribute + { + } + """; + + context.AddSource("ValidatableTypeAttribute.g.cs", validatableTypeAttributeSource); + } } diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs new file mode 100644 index 000000000000..c549b402510b --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs @@ -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 Microsoft.Extensions.Validation.GeneratorTests; +using VerifyXunit; +using Xunit; + +namespace Microsoft.Extensions.Validation.GeneratorTests; + +[UsesVerify] +public partial class ValidationsGeneratorTests : ValidationsGeneratorTestBase +{ + [Fact] + public async Task CanUseAutoGeneratedValidatableTypeAttribute() + { + var source = """ + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using System.ComponentModel.DataAnnotations; + + var builder = WebApplication.CreateBuilder(); + builder.Services.AddValidation(); + var app = builder.Build(); + + // Using the auto-generated ValidatableTypeAttribute + // No manual attribute definition needed! + [ValidatableType] + public class Customer + { + [Required] + public string Name { get; set; } = ""; + + [EmailAddress] + public string Email { get; set; } = ""; + } + + app.MapPost("/customers", (Customer customer) => "OK"); + + app.Run(); + """; + + await Verify(source, out var compilation); + } +} diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#EmbeddedAttribute.g.verified.cs new file mode 100644 index 000000000000..03190c5b702f --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#EmbeddedAttribute.g.verified.cs @@ -0,0 +1,9 @@ +//HintName: EmbeddedAttribute.g.cs +// +namespace Microsoft.CodeAnalysis +{ + [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class EmbeddedAttribute : global::System.Attribute + { + } +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableInfoResolver.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableInfoResolver.g.verified.cs new file mode 100644 index 000000000000..0ba2323165fc --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableInfoResolver.g.verified.cs @@ -0,0 +1,170 @@ +//HintName: ValidatableInfoResolver.g.cs +#nullable enable annotations +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +#nullable enable +#pragma warning disable ASP0029 + +namespace System.Runtime.CompilerServices +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : System.Attribute + { + public InterceptsLocationAttribute(int version, string data) + { + } + } +} + +namespace Microsoft.Extensions.Validation.Generated +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file sealed class GeneratedValidatablePropertyInfo : global::Microsoft.Extensions.Validation.ValidatablePropertyInfo + { + public GeneratedValidatablePropertyInfo( + [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + global::System.Type containingType, + global::System.Type propertyType, + string name, + string displayName) : base(containingType, propertyType, name, displayName) + { + ContainingType = containingType; + Name = name; + } + + [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + internal global::System.Type ContainingType { get; } + internal string Name { get; } + + protected override global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes() + => ValidationAttributeCache.GetValidationAttributes(ContainingType, Name); + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file sealed class GeneratedValidatableTypeInfo : global::Microsoft.Extensions.Validation.ValidatableTypeInfo + { + public GeneratedValidatableTypeInfo( + [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.Interfaces)] + global::System.Type type, + ValidatablePropertyInfo[] members) : base(type, members) { } + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file class GeneratedValidatableInfoResolver : global::Microsoft.Extensions.Validation.IValidatableInfoResolver + { + public bool TryGetValidatableTypeInfo(global::System.Type type, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo) + { + validatableInfo = null; + if (type == typeof(global::Customer)) + { + validatableInfo = new GeneratedValidatableTypeInfo( + type: typeof(global::Customer), + members: [ + new GeneratedValidatablePropertyInfo( + containingType: typeof(global::Customer), + propertyType: typeof(string), + name: "Name", + displayName: "Name" + ), + new GeneratedValidatablePropertyInfo( + containingType: typeof(global::Customer), + propertyType: typeof(string), + name: "Email", + displayName: "Email" + ), + ] + ); + return true; + } + + return false; + } + + // No-ops, rely on runtime code for ParameterInfo-based resolution + public bool TryGetValidatableParameterInfo(global::System.Reflection.ParameterInfo parameterInfo, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo) + { + validatableInfo = null; + return false; + } + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class GeneratedServiceCollectionExtensions + { + [InterceptsLocation] + public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddValidation(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::System.Action? configureOptions = null) + { + // Use non-extension method to avoid infinite recursion. + return global::Microsoft.Extensions.DependencyInjection.ValidationServiceCollectionExtensions.AddValidation(services, options => + { + options.Resolvers.Insert(0, new GeneratedValidatableInfoResolver()); + if (configureOptions is not null) + { + configureOptions(options); + } + }); + } + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class ValidationAttributeCache + { + private sealed record CacheKey( + [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + [property: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + global::System.Type ContainingType, + string PropertyName); + private static readonly global::System.Collections.Concurrent.ConcurrentDictionary _cache = new(); + + public static global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes( + [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + global::System.Type containingType, + string propertyName) + { + var key = new CacheKey(containingType, propertyName); + return _cache.GetOrAdd(key, static k => + { + var results = new global::System.Collections.Generic.List(); + + // Get attributes from the property + var property = k.ContainingType.GetProperty(k.PropertyName); + if (property != null) + { + var propertyAttributes = global::System.Reflection.CustomAttributeExtensions + .GetCustomAttributes(property, inherit: true); + + results.AddRange(propertyAttributes); + } + + // Check constructors for parameters that match the property name + // to handle record scenarios + foreach (var constructor in k.ContainingType.GetConstructors()) + { + // Look for parameter with matching name (case insensitive) + var parameter = global::System.Linq.Enumerable.FirstOrDefault( + constructor.GetParameters(), + p => string.Equals(p.Name, k.PropertyName, global::System.StringComparison.OrdinalIgnoreCase)); + + if (parameter != null) + { + var paramAttributes = global::System.Reflection.CustomAttributeExtensions + .GetCustomAttributes(parameter, inherit: true); + + results.AddRange(paramAttributes); + + break; + } + } + + return results.ToArray(); + }); + } + } +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableTypeAttribute.g.verified.cs new file mode 100644 index 000000000000..480eb60b7b9b --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableTypeAttribute.g.verified.cs @@ -0,0 +1,7 @@ +//HintName: ValidatableTypeAttribute.g.cs +// +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +[global::System.AttributeUsage(global::System.AttributeTargets.Class)] +internal sealed class ValidatableTypeAttribute : global::System.Attribute +{ +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateClassTypesWithAttribute#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateClassTypesWithAttribute#EmbeddedAttribute.g.verified.cs new file mode 100644 index 000000000000..03190c5b702f --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateClassTypesWithAttribute#EmbeddedAttribute.g.verified.cs @@ -0,0 +1,9 @@ +//HintName: EmbeddedAttribute.g.cs +// +namespace Microsoft.CodeAnalysis +{ + [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class EmbeddedAttribute : global::System.Attribute + { + } +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateClassTypesWithAttribute#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateClassTypesWithAttribute#ValidatableTypeAttribute.g.verified.cs new file mode 100644 index 000000000000..480eb60b7b9b --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateClassTypesWithAttribute#ValidatableTypeAttribute.g.verified.cs @@ -0,0 +1,7 @@ +//HintName: ValidatableTypeAttribute.g.cs +// +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +[global::System.AttributeUsage(global::System.AttributeTargets.Class)] +internal sealed class ValidatableTypeAttribute : global::System.Attribute +{ +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypes#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypes#EmbeddedAttribute.g.verified.cs new file mode 100644 index 000000000000..03190c5b702f --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypes#EmbeddedAttribute.g.verified.cs @@ -0,0 +1,9 @@ +//HintName: EmbeddedAttribute.g.cs +// +namespace Microsoft.CodeAnalysis +{ + [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class EmbeddedAttribute : global::System.Attribute + { + } +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypes#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypes#ValidatableTypeAttribute.g.verified.cs new file mode 100644 index 000000000000..480eb60b7b9b --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypes#ValidatableTypeAttribute.g.verified.cs @@ -0,0 +1,7 @@ +//HintName: ValidatableTypeAttribute.g.cs +// +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +[global::System.AttributeUsage(global::System.AttributeTargets.Class)] +internal sealed class ValidatableTypeAttribute : global::System.Attribute +{ +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateIValidatableObject#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateIValidatableObject#EmbeddedAttribute.g.verified.cs new file mode 100644 index 000000000000..03190c5b702f --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateIValidatableObject#EmbeddedAttribute.g.verified.cs @@ -0,0 +1,9 @@ +//HintName: EmbeddedAttribute.g.cs +// +namespace Microsoft.CodeAnalysis +{ + [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class EmbeddedAttribute : global::System.Attribute + { + } +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateIValidatableObject#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateIValidatableObject#ValidatableTypeAttribute.g.verified.cs new file mode 100644 index 000000000000..480eb60b7b9b --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateIValidatableObject#ValidatableTypeAttribute.g.verified.cs @@ -0,0 +1,7 @@ +//HintName: ValidatableTypeAttribute.g.cs +// +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +[global::System.AttributeUsage(global::System.AttributeTargets.Class)] +internal sealed class ValidatableTypeAttribute : global::System.Attribute +{ +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateMultipleNamespaces#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateMultipleNamespaces#EmbeddedAttribute.g.verified.cs new file mode 100644 index 000000000000..03190c5b702f --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateMultipleNamespaces#EmbeddedAttribute.g.verified.cs @@ -0,0 +1,9 @@ +//HintName: EmbeddedAttribute.g.cs +// +namespace Microsoft.CodeAnalysis +{ + [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class EmbeddedAttribute : global::System.Attribute + { + } +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateMultipleNamespaces#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateMultipleNamespaces#ValidatableTypeAttribute.g.verified.cs new file mode 100644 index 000000000000..480eb60b7b9b --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateMultipleNamespaces#ValidatableTypeAttribute.g.verified.cs @@ -0,0 +1,7 @@ +//HintName: ValidatableTypeAttribute.g.cs +// +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +[global::System.AttributeUsage(global::System.AttributeTargets.Class)] +internal sealed class ValidatableTypeAttribute : global::System.Attribute +{ +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateParameters#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateParameters#EmbeddedAttribute.g.verified.cs new file mode 100644 index 000000000000..03190c5b702f --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateParameters#EmbeddedAttribute.g.verified.cs @@ -0,0 +1,9 @@ +//HintName: EmbeddedAttribute.g.cs +// +namespace Microsoft.CodeAnalysis +{ + [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class EmbeddedAttribute : global::System.Attribute + { + } +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateParameters#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateParameters#ValidatableTypeAttribute.g.verified.cs new file mode 100644 index 000000000000..480eb60b7b9b --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateParameters#ValidatableTypeAttribute.g.verified.cs @@ -0,0 +1,7 @@ +//HintName: ValidatableTypeAttribute.g.cs +// +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +[global::System.AttributeUsage(global::System.AttributeTargets.Class)] +internal sealed class ValidatableTypeAttribute : global::System.Attribute +{ +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidatePolymorphicTypes#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidatePolymorphicTypes#EmbeddedAttribute.g.verified.cs new file mode 100644 index 000000000000..03190c5b702f --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidatePolymorphicTypes#EmbeddedAttribute.g.verified.cs @@ -0,0 +1,9 @@ +//HintName: EmbeddedAttribute.g.cs +// +namespace Microsoft.CodeAnalysis +{ + [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class EmbeddedAttribute : global::System.Attribute + { + } +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidatePolymorphicTypes#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidatePolymorphicTypes#ValidatableTypeAttribute.g.verified.cs new file mode 100644 index 000000000000..480eb60b7b9b --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidatePolymorphicTypes#ValidatableTypeAttribute.g.verified.cs @@ -0,0 +1,7 @@ +//HintName: ValidatableTypeAttribute.g.cs +// +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +[global::System.AttributeUsage(global::System.AttributeTargets.Class)] +internal sealed class ValidatableTypeAttribute : global::System.Attribute +{ +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypes#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypes#EmbeddedAttribute.g.verified.cs new file mode 100644 index 000000000000..03190c5b702f --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypes#EmbeddedAttribute.g.verified.cs @@ -0,0 +1,9 @@ +//HintName: EmbeddedAttribute.g.cs +// +namespace Microsoft.CodeAnalysis +{ + [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class EmbeddedAttribute : global::System.Attribute + { + } +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypes#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypes#ValidatableTypeAttribute.g.verified.cs new file mode 100644 index 000000000000..480eb60b7b9b --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypes#ValidatableTypeAttribute.g.verified.cs @@ -0,0 +1,7 @@ +//HintName: ValidatableTypeAttribute.g.cs +// +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +[global::System.AttributeUsage(global::System.AttributeTargets.Class)] +internal sealed class ValidatableTypeAttribute : global::System.Attribute +{ +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypesWithAttribute#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypesWithAttribute#EmbeddedAttribute.g.verified.cs new file mode 100644 index 000000000000..03190c5b702f --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypesWithAttribute#EmbeddedAttribute.g.verified.cs @@ -0,0 +1,9 @@ +//HintName: EmbeddedAttribute.g.cs +// +namespace Microsoft.CodeAnalysis +{ + [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class EmbeddedAttribute : global::System.Attribute + { + } +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypesWithAttribute#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypesWithAttribute#ValidatableTypeAttribute.g.verified.cs new file mode 100644 index 000000000000..480eb60b7b9b --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypesWithAttribute#ValidatableTypeAttribute.g.verified.cs @@ -0,0 +1,7 @@ +//HintName: ValidatableTypeAttribute.g.cs +// +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +[global::System.AttributeUsage(global::System.AttributeTargets.Class)] +internal sealed class ValidatableTypeAttribute : global::System.Attribute +{ +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecursiveTypes#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecursiveTypes#EmbeddedAttribute.g.verified.cs new file mode 100644 index 000000000000..03190c5b702f --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecursiveTypes#EmbeddedAttribute.g.verified.cs @@ -0,0 +1,9 @@ +//HintName: EmbeddedAttribute.g.cs +// +namespace Microsoft.CodeAnalysis +{ + [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class EmbeddedAttribute : global::System.Attribute + { + } +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecursiveTypes#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecursiveTypes#ValidatableTypeAttribute.g.verified.cs new file mode 100644 index 000000000000..480eb60b7b9b --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecursiveTypes#ValidatableTypeAttribute.g.verified.cs @@ -0,0 +1,7 @@ +//HintName: ValidatableTypeAttribute.g.cs +// +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +[global::System.AttributeUsage(global::System.AttributeTargets.Class)] +internal sealed class ValidatableTypeAttribute : global::System.Attribute +{ +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#EmbeddedAttribute.g.verified.cs new file mode 100644 index 000000000000..03190c5b702f --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#EmbeddedAttribute.g.verified.cs @@ -0,0 +1,9 @@ +//HintName: EmbeddedAttribute.g.cs +// +namespace Microsoft.CodeAnalysis +{ + [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class EmbeddedAttribute : global::System.Attribute + { + } +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#ValidatableTypeAttribute.g.verified.cs new file mode 100644 index 000000000000..480eb60b7b9b --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#ValidatableTypeAttribute.g.verified.cs @@ -0,0 +1,7 @@ +//HintName: ValidatableTypeAttribute.g.cs +// +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +[global::System.AttributeUsage(global::System.AttributeTargets.Class)] +internal sealed class ValidatableTypeAttribute : global::System.Attribute +{ +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitForExemptTypes#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitForExemptTypes#EmbeddedAttribute.g.verified.cs new file mode 100644 index 000000000000..03190c5b702f --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitForExemptTypes#EmbeddedAttribute.g.verified.cs @@ -0,0 +1,9 @@ +//HintName: EmbeddedAttribute.g.cs +// +namespace Microsoft.CodeAnalysis +{ + [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class EmbeddedAttribute : global::System.Attribute + { + } +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitForExemptTypes#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitForExemptTypes#ValidatableTypeAttribute.g.verified.cs new file mode 100644 index 000000000000..480eb60b7b9b --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitForExemptTypes#ValidatableTypeAttribute.g.verified.cs @@ -0,0 +1,7 @@ +//HintName: ValidatableTypeAttribute.g.cs +// +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +[global::System.AttributeUsage(global::System.AttributeTargets.Class)] +internal sealed class ValidatableTypeAttribute : global::System.Attribute +{ +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNoAddValidationCallExists#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNoAddValidationCallExists#EmbeddedAttribute.g.verified.cs new file mode 100644 index 000000000000..03190c5b702f --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNoAddValidationCallExists#EmbeddedAttribute.g.verified.cs @@ -0,0 +1,9 @@ +//HintName: EmbeddedAttribute.g.cs +// +namespace Microsoft.CodeAnalysis +{ + [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class EmbeddedAttribute : global::System.Attribute + { + } +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNoAddValidationCallExists#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNoAddValidationCallExists#ValidatableTypeAttribute.g.verified.cs new file mode 100644 index 000000000000..480eb60b7b9b --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNoAddValidationCallExists#ValidatableTypeAttribute.g.verified.cs @@ -0,0 +1,7 @@ +//HintName: ValidatableTypeAttribute.g.cs +// +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +[global::System.AttributeUsage(global::System.AttributeTargets.Class)] +internal sealed class ValidatableTypeAttribute : global::System.Attribute +{ +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNotCorrectAddValidationCallExists#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNotCorrectAddValidationCallExists#EmbeddedAttribute.g.verified.cs new file mode 100644 index 000000000000..03190c5b702f --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNotCorrectAddValidationCallExists#EmbeddedAttribute.g.verified.cs @@ -0,0 +1,9 @@ +//HintName: EmbeddedAttribute.g.cs +// +namespace Microsoft.CodeAnalysis +{ + [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class EmbeddedAttribute : global::System.Attribute + { + } +} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNotCorrectAddValidationCallExists#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNotCorrectAddValidationCallExists#ValidatableTypeAttribute.g.verified.cs new file mode 100644 index 000000000000..480eb60b7b9b --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNotCorrectAddValidationCallExists#ValidatableTypeAttribute.g.verified.cs @@ -0,0 +1,7 @@ +//HintName: ValidatableTypeAttribute.g.cs +// +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +[global::System.AttributeUsage(global::System.AttributeTargets.Class)] +internal sealed class ValidatableTypeAttribute : global::System.Attribute +{ +} \ No newline at end of file From 954c617c822ae5a0187e748193ddc7c168c577f4 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Tue, 5 Aug 2025 12:07:20 +0200 Subject: [PATCH 02/12] Only emit attribute if the root project namespace is provided and GenerateBuiltinValidatableTypeAttribute is defined --- src/Validation/gen/ValidationsGenerator.cs | 35 ++++++++++--- ...dationsGenerator.AutoGeneratedAttribute.cs | 6 ++- .../ValidationsGeneratorTestBase.cs | 52 ++++++++++++++++++- ...ute#ValidatableTypeAttribute.g.verified.cs | 2 + 4 files changed, 87 insertions(+), 8 deletions(-) diff --git a/src/Validation/gen/ValidationsGenerator.cs b/src/Validation/gen/ValidationsGenerator.cs index bac7b31f9a4e..58542ed0e48c 100644 --- a/src/Validation/gen/ValidationsGenerator.cs +++ b/src/Validation/gen/ValidationsGenerator.cs @@ -1,6 +1,7 @@ // 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.Linq; using Microsoft.CodeAnalysis; @@ -11,8 +12,21 @@ public sealed partial class ValidationsGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { - // Emit the ValidatableTypeAttribute with embedded support - context.RegisterPostInitializationOutput(EmitValidatableTypeAttribute); + // Get the MSBuild properties for controlling attribute generation + var generatorSettings = context.AnalyzerConfigOptionsProvider + .Select((configOptions, _) => + { + configOptions.GlobalOptions.TryGetValue("build_property.RootNamespace", out var rootNamespace); + configOptions.GlobalOptions.TryGetValue("build_property.GenerateBuiltinValidatableTypeAttribute", out var generateAttribute); + + var shouldGenerate = !string.IsNullOrEmpty(rootNamespace) && + string.Equals(generateAttribute, "true", StringComparison.OrdinalIgnoreCase); + + return new GeneratorSettings(rootNamespace, shouldGenerate); + }); + + // Emit the ValidatableTypeAttribute conditionally + context.RegisterSourceOutput(generatorSettings, EmitValidatableTypeAttribute); // Find the builder.Services.AddValidation() call in the application. var addValidation = context.SyntaxProvider.CreateSyntaxProvider( @@ -48,8 +62,14 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.RegisterSourceOutput(emitInputs, Emit); } - private static void EmitValidatableTypeAttribute(IncrementalGeneratorPostInitializationContext context) + private static void EmitValidatableTypeAttribute(SourceProductionContext context, GeneratorSettings settings) { + // Only emit if the build property is set to true and root namespace is provided + if (!settings.ShouldGenerateAttribute) + { + return; + } + // First, emit the EmbeddedAttribute if it doesn't exist var embeddedAttributeSource = """ // @@ -64,10 +84,11 @@ internal sealed class EmbeddedAttribute : global::System.Attribute context.AddSource("EmbeddedAttribute.g.cs", embeddedAttributeSource); - // Then emit the ValidatableTypeAttribute in the global namespace - // We use global namespace since we don't have access to the project's root namespace here - var validatableTypeAttributeSource = """ + // Generate the ValidatableTypeAttribute in the project's root namespace + var validatableTypeAttributeSource = $$""" // + namespace {{settings.RootNamespace}}; + [global::Microsoft.CodeAnalysis.EmbeddedAttribute] [global::System.AttributeUsage(global::System.AttributeTargets.Class)] internal sealed class ValidatableTypeAttribute : global::System.Attribute @@ -77,4 +98,6 @@ internal sealed class ValidatableTypeAttribute : global::System.Attribute context.AddSource("ValidatableTypeAttribute.g.cs", validatableTypeAttributeSource); } + + private readonly record struct GeneratorSettings(string? RootNamespace, bool ShouldGenerateAttribute); } diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs index c549b402510b..7321e82a4102 100644 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs @@ -39,6 +39,10 @@ public class Customer app.Run(); """; - await Verify(source, out var compilation); + await Verify(source, out var compilation, globalOptions: new Dictionary + { + ["build_property.RootNamespace"] = "TestApp", + ["build_property.GenerateBuiltinValidatableTypeAttribute"] = "true" + }); } } diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGeneratorTestBase.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGeneratorTestBase.cs index 81500f090c96..3af13a1d2fc9 100644 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGeneratorTestBase.cs +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGeneratorTestBase.cs @@ -1,4 +1,5 @@ #pragma warning disable ASP0029 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +#nullable disable // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. @@ -22,6 +23,7 @@ using Microsoft.AspNetCore.Http.Features.Authentication; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Text; using Microsoft.Extensions.DependencyInjection; @@ -43,6 +45,9 @@ public partial class ValidationsGeneratorTestBase : LoggedTestBase .WithFeatures([new KeyValuePair("InterceptorsNamespaces", "Microsoft.Extensions.Validation.Generated")]); internal static Task Verify(string source, out Compilation compilation) + => Verify(source, out compilation, globalOptions: null); + + internal static Task Verify(string source, out Compilation compilation, Dictionary globalOptions) { var references = AppDomain.CurrentDomain.GetAssemblies() .Where(assembly => !assembly.IsDynamic && !string.IsNullOrWhiteSpace(assembly.Location)) @@ -77,7 +82,21 @@ internal static Task Verify(string source, out Compilation compilation) references, new CSharpCompilationOptions(OutputKind.ConsoleApplication)); var generator = new ValidationsGenerator(); - var driver = CSharpGeneratorDriver.Create(generators: [generator.AsSourceGenerator()], parseOptions: ParseOptions); + + // Create the driver with optional global options + CSharpGeneratorDriver driver; + if (globalOptions != null) + { + var analyzerConfig = new InMemoryAnalyzerConfigOptionsProvider(globalOptions); + driver = CSharpGeneratorDriver.Create( + generators: [generator.AsSourceGenerator()], + parseOptions: ParseOptions, + optionsProvider: analyzerConfig); + } + else + { + driver = CSharpGeneratorDriver.Create(generators: [generator.AsSourceGenerator()], parseOptions: ParseOptions); + } return Verifier .Verify(driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out compilation, out var diagnostics)) .ScrubLinesWithReplace(line => InterceptsLocationRegex().Replace(line, "[InterceptsLocation]")) @@ -603,4 +622,35 @@ public RequestBodyDetectionFeature(bool canHaveBody) public bool CanHaveBody { get; } } + + internal sealed class InMemoryAnalyzerConfigOptionsProvider : AnalyzerConfigOptionsProvider + { + private readonly InMemoryAnalyzerConfigOptions _globalOptions; + + public InMemoryAnalyzerConfigOptionsProvider(Dictionary globalOptions) + { + _globalOptions = new InMemoryAnalyzerConfigOptions(globalOptions); + } + + public override AnalyzerConfigOptions GlobalOptions => _globalOptions; + + public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => _globalOptions; + + public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => _globalOptions; + } + + internal sealed class InMemoryAnalyzerConfigOptions : AnalyzerConfigOptions + { + private readonly Dictionary _options; + + public InMemoryAnalyzerConfigOptions(Dictionary options) + { + _options = options; + } + + public override bool TryGetValue(string key, out string value) + { + return _options.TryGetValue(key, out value!); + } + } } diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableTypeAttribute.g.verified.cs index 480eb60b7b9b..7a46d42ccf31 100644 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableTypeAttribute.g.verified.cs +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableTypeAttribute.g.verified.cs @@ -1,5 +1,7 @@ //HintName: ValidatableTypeAttribute.g.cs // +namespace TestApp; + [global::Microsoft.CodeAnalysis.EmbeddedAttribute] [global::System.AttributeUsage(global::System.AttributeTargets.Class)] internal sealed class ValidatableTypeAttribute : global::System.Attribute From 0a86cb3c8d5067298c46b7b0f67eb49daafaea50 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Tue, 5 Aug 2025 14:05:42 +0200 Subject: [PATCH 03/12] Cleanup --- ...Microsoft.Extensions.Validation.GeneratorTests.csproj | 2 ++ ...assTypesWithAttribute#EmbeddedAttribute.g.verified.cs | 9 --------- ...sWithAttribute#ValidatableTypeAttribute.g.verified.cs | 7 ------- ...nValidateComplexTypes#EmbeddedAttribute.g.verified.cs | 9 --------- ...teComplexTypes#ValidatableTypeAttribute.g.verified.cs | 7 ------- ...ateIValidatableObject#EmbeddedAttribute.g.verified.cs | 9 --------- ...idatableObject#ValidatableTypeAttribute.g.verified.cs | 7 ------- ...ateMultipleNamespaces#EmbeddedAttribute.g.verified.cs | 9 --------- ...ipleNamespaces#ValidatableTypeAttribute.g.verified.cs | 7 ------- ...CanValidateParameters#EmbeddedAttribute.g.verified.cs | 9 --------- ...dateParameters#ValidatableTypeAttribute.g.verified.cs | 7 ------- ...idatePolymorphicTypes#EmbeddedAttribute.g.verified.cs | 9 --------- ...lymorphicTypes#ValidatableTypeAttribute.g.verified.cs | 7 ------- ...anValidateRecordTypes#EmbeddedAttribute.g.verified.cs | 9 --------- ...ateRecordTypes#ValidatableTypeAttribute.g.verified.cs | 7 ------- ...ordTypesWithAttribute#EmbeddedAttribute.g.verified.cs | 9 --------- ...sWithAttribute#ValidatableTypeAttribute.g.verified.cs | 7 ------- ...alidateRecursiveTypes#EmbeddedAttribute.g.verified.cs | 9 --------- ...RecursiveTypes#ValidatableTypeAttribute.g.verified.cs | 7 ------- ...ithParsableProperties#EmbeddedAttribute.g.verified.cs | 9 --------- ...ableProperties#ValidatableTypeAttribute.g.verified.cs | 7 ------- ...NotEmitForExemptTypes#EmbeddedAttribute.g.verified.cs | 9 --------- ...ForExemptTypes#ValidatableTypeAttribute.g.verified.cs | 7 ------- ...dValidationCallExists#EmbeddedAttribute.g.verified.cs | 9 --------- ...tionCallExists#ValidatableTypeAttribute.g.verified.cs | 7 ------- ...dValidationCallExists#EmbeddedAttribute.g.verified.cs | 9 --------- ...tionCallExists#ValidatableTypeAttribute.g.verified.cs | 7 ------- 27 files changed, 2 insertions(+), 208 deletions(-) delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateClassTypesWithAttribute#EmbeddedAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateClassTypesWithAttribute#ValidatableTypeAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypes#EmbeddedAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypes#ValidatableTypeAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateIValidatableObject#EmbeddedAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateIValidatableObject#ValidatableTypeAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateMultipleNamespaces#EmbeddedAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateMultipleNamespaces#ValidatableTypeAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateParameters#EmbeddedAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateParameters#ValidatableTypeAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidatePolymorphicTypes#EmbeddedAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidatePolymorphicTypes#ValidatableTypeAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypes#EmbeddedAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypes#ValidatableTypeAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypesWithAttribute#EmbeddedAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypesWithAttribute#ValidatableTypeAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecursiveTypes#EmbeddedAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecursiveTypes#ValidatableTypeAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#EmbeddedAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#ValidatableTypeAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitForExemptTypes#EmbeddedAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitForExemptTypes#ValidatableTypeAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNoAddValidationCallExists#EmbeddedAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNoAddValidationCallExists#ValidatableTypeAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNotCorrectAddValidationCallExists#EmbeddedAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNotCorrectAddValidationCallExists#ValidatableTypeAttribute.g.verified.cs diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/Microsoft.Extensions.Validation.GeneratorTests.csproj b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/Microsoft.Extensions.Validation.GeneratorTests.csproj index db08d20e21a2..5af4c2ccda9b 100644 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/Microsoft.Extensions.Validation.GeneratorTests.csproj +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/Microsoft.Extensions.Validation.GeneratorTests.csproj @@ -28,6 +28,8 @@ + + diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateClassTypesWithAttribute#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateClassTypesWithAttribute#EmbeddedAttribute.g.verified.cs deleted file mode 100644 index 03190c5b702f..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateClassTypesWithAttribute#EmbeddedAttribute.g.verified.cs +++ /dev/null @@ -1,9 +0,0 @@ -//HintName: EmbeddedAttribute.g.cs -// -namespace Microsoft.CodeAnalysis -{ - [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class EmbeddedAttribute : global::System.Attribute - { - } -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateClassTypesWithAttribute#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateClassTypesWithAttribute#ValidatableTypeAttribute.g.verified.cs deleted file mode 100644 index 480eb60b7b9b..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateClassTypesWithAttribute#ValidatableTypeAttribute.g.verified.cs +++ /dev/null @@ -1,7 +0,0 @@ -//HintName: ValidatableTypeAttribute.g.cs -// -[global::Microsoft.CodeAnalysis.EmbeddedAttribute] -[global::System.AttributeUsage(global::System.AttributeTargets.Class)] -internal sealed class ValidatableTypeAttribute : global::System.Attribute -{ -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypes#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypes#EmbeddedAttribute.g.verified.cs deleted file mode 100644 index 03190c5b702f..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypes#EmbeddedAttribute.g.verified.cs +++ /dev/null @@ -1,9 +0,0 @@ -//HintName: EmbeddedAttribute.g.cs -// -namespace Microsoft.CodeAnalysis -{ - [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class EmbeddedAttribute : global::System.Attribute - { - } -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypes#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypes#ValidatableTypeAttribute.g.verified.cs deleted file mode 100644 index 480eb60b7b9b..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypes#ValidatableTypeAttribute.g.verified.cs +++ /dev/null @@ -1,7 +0,0 @@ -//HintName: ValidatableTypeAttribute.g.cs -// -[global::Microsoft.CodeAnalysis.EmbeddedAttribute] -[global::System.AttributeUsage(global::System.AttributeTargets.Class)] -internal sealed class ValidatableTypeAttribute : global::System.Attribute -{ -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateIValidatableObject#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateIValidatableObject#EmbeddedAttribute.g.verified.cs deleted file mode 100644 index 03190c5b702f..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateIValidatableObject#EmbeddedAttribute.g.verified.cs +++ /dev/null @@ -1,9 +0,0 @@ -//HintName: EmbeddedAttribute.g.cs -// -namespace Microsoft.CodeAnalysis -{ - [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class EmbeddedAttribute : global::System.Attribute - { - } -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateIValidatableObject#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateIValidatableObject#ValidatableTypeAttribute.g.verified.cs deleted file mode 100644 index 480eb60b7b9b..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateIValidatableObject#ValidatableTypeAttribute.g.verified.cs +++ /dev/null @@ -1,7 +0,0 @@ -//HintName: ValidatableTypeAttribute.g.cs -// -[global::Microsoft.CodeAnalysis.EmbeddedAttribute] -[global::System.AttributeUsage(global::System.AttributeTargets.Class)] -internal sealed class ValidatableTypeAttribute : global::System.Attribute -{ -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateMultipleNamespaces#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateMultipleNamespaces#EmbeddedAttribute.g.verified.cs deleted file mode 100644 index 03190c5b702f..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateMultipleNamespaces#EmbeddedAttribute.g.verified.cs +++ /dev/null @@ -1,9 +0,0 @@ -//HintName: EmbeddedAttribute.g.cs -// -namespace Microsoft.CodeAnalysis -{ - [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class EmbeddedAttribute : global::System.Attribute - { - } -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateMultipleNamespaces#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateMultipleNamespaces#ValidatableTypeAttribute.g.verified.cs deleted file mode 100644 index 480eb60b7b9b..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateMultipleNamespaces#ValidatableTypeAttribute.g.verified.cs +++ /dev/null @@ -1,7 +0,0 @@ -//HintName: ValidatableTypeAttribute.g.cs -// -[global::Microsoft.CodeAnalysis.EmbeddedAttribute] -[global::System.AttributeUsage(global::System.AttributeTargets.Class)] -internal sealed class ValidatableTypeAttribute : global::System.Attribute -{ -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateParameters#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateParameters#EmbeddedAttribute.g.verified.cs deleted file mode 100644 index 03190c5b702f..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateParameters#EmbeddedAttribute.g.verified.cs +++ /dev/null @@ -1,9 +0,0 @@ -//HintName: EmbeddedAttribute.g.cs -// -namespace Microsoft.CodeAnalysis -{ - [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class EmbeddedAttribute : global::System.Attribute - { - } -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateParameters#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateParameters#ValidatableTypeAttribute.g.verified.cs deleted file mode 100644 index 480eb60b7b9b..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateParameters#ValidatableTypeAttribute.g.verified.cs +++ /dev/null @@ -1,7 +0,0 @@ -//HintName: ValidatableTypeAttribute.g.cs -// -[global::Microsoft.CodeAnalysis.EmbeddedAttribute] -[global::System.AttributeUsage(global::System.AttributeTargets.Class)] -internal sealed class ValidatableTypeAttribute : global::System.Attribute -{ -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidatePolymorphicTypes#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidatePolymorphicTypes#EmbeddedAttribute.g.verified.cs deleted file mode 100644 index 03190c5b702f..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidatePolymorphicTypes#EmbeddedAttribute.g.verified.cs +++ /dev/null @@ -1,9 +0,0 @@ -//HintName: EmbeddedAttribute.g.cs -// -namespace Microsoft.CodeAnalysis -{ - [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class EmbeddedAttribute : global::System.Attribute - { - } -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidatePolymorphicTypes#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidatePolymorphicTypes#ValidatableTypeAttribute.g.verified.cs deleted file mode 100644 index 480eb60b7b9b..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidatePolymorphicTypes#ValidatableTypeAttribute.g.verified.cs +++ /dev/null @@ -1,7 +0,0 @@ -//HintName: ValidatableTypeAttribute.g.cs -// -[global::Microsoft.CodeAnalysis.EmbeddedAttribute] -[global::System.AttributeUsage(global::System.AttributeTargets.Class)] -internal sealed class ValidatableTypeAttribute : global::System.Attribute -{ -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypes#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypes#EmbeddedAttribute.g.verified.cs deleted file mode 100644 index 03190c5b702f..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypes#EmbeddedAttribute.g.verified.cs +++ /dev/null @@ -1,9 +0,0 @@ -//HintName: EmbeddedAttribute.g.cs -// -namespace Microsoft.CodeAnalysis -{ - [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class EmbeddedAttribute : global::System.Attribute - { - } -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypes#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypes#ValidatableTypeAttribute.g.verified.cs deleted file mode 100644 index 480eb60b7b9b..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypes#ValidatableTypeAttribute.g.verified.cs +++ /dev/null @@ -1,7 +0,0 @@ -//HintName: ValidatableTypeAttribute.g.cs -// -[global::Microsoft.CodeAnalysis.EmbeddedAttribute] -[global::System.AttributeUsage(global::System.AttributeTargets.Class)] -internal sealed class ValidatableTypeAttribute : global::System.Attribute -{ -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypesWithAttribute#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypesWithAttribute#EmbeddedAttribute.g.verified.cs deleted file mode 100644 index 03190c5b702f..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypesWithAttribute#EmbeddedAttribute.g.verified.cs +++ /dev/null @@ -1,9 +0,0 @@ -//HintName: EmbeddedAttribute.g.cs -// -namespace Microsoft.CodeAnalysis -{ - [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class EmbeddedAttribute : global::System.Attribute - { - } -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypesWithAttribute#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypesWithAttribute#ValidatableTypeAttribute.g.verified.cs deleted file mode 100644 index 480eb60b7b9b..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecordTypesWithAttribute#ValidatableTypeAttribute.g.verified.cs +++ /dev/null @@ -1,7 +0,0 @@ -//HintName: ValidatableTypeAttribute.g.cs -// -[global::Microsoft.CodeAnalysis.EmbeddedAttribute] -[global::System.AttributeUsage(global::System.AttributeTargets.Class)] -internal sealed class ValidatableTypeAttribute : global::System.Attribute -{ -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecursiveTypes#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecursiveTypes#EmbeddedAttribute.g.verified.cs deleted file mode 100644 index 03190c5b702f..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecursiveTypes#EmbeddedAttribute.g.verified.cs +++ /dev/null @@ -1,9 +0,0 @@ -//HintName: EmbeddedAttribute.g.cs -// -namespace Microsoft.CodeAnalysis -{ - [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class EmbeddedAttribute : global::System.Attribute - { - } -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecursiveTypes#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecursiveTypes#ValidatableTypeAttribute.g.verified.cs deleted file mode 100644 index 480eb60b7b9b..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateRecursiveTypes#ValidatableTypeAttribute.g.verified.cs +++ /dev/null @@ -1,7 +0,0 @@ -//HintName: ValidatableTypeAttribute.g.cs -// -[global::Microsoft.CodeAnalysis.EmbeddedAttribute] -[global::System.AttributeUsage(global::System.AttributeTargets.Class)] -internal sealed class ValidatableTypeAttribute : global::System.Attribute -{ -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#EmbeddedAttribute.g.verified.cs deleted file mode 100644 index 03190c5b702f..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#EmbeddedAttribute.g.verified.cs +++ /dev/null @@ -1,9 +0,0 @@ -//HintName: EmbeddedAttribute.g.cs -// -namespace Microsoft.CodeAnalysis -{ - [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class EmbeddedAttribute : global::System.Attribute - { - } -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#ValidatableTypeAttribute.g.verified.cs deleted file mode 100644 index 480eb60b7b9b..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#ValidatableTypeAttribute.g.verified.cs +++ /dev/null @@ -1,7 +0,0 @@ -//HintName: ValidatableTypeAttribute.g.cs -// -[global::Microsoft.CodeAnalysis.EmbeddedAttribute] -[global::System.AttributeUsage(global::System.AttributeTargets.Class)] -internal sealed class ValidatableTypeAttribute : global::System.Attribute -{ -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitForExemptTypes#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitForExemptTypes#EmbeddedAttribute.g.verified.cs deleted file mode 100644 index 03190c5b702f..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitForExemptTypes#EmbeddedAttribute.g.verified.cs +++ /dev/null @@ -1,9 +0,0 @@ -//HintName: EmbeddedAttribute.g.cs -// -namespace Microsoft.CodeAnalysis -{ - [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class EmbeddedAttribute : global::System.Attribute - { - } -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitForExemptTypes#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitForExemptTypes#ValidatableTypeAttribute.g.verified.cs deleted file mode 100644 index 480eb60b7b9b..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitForExemptTypes#ValidatableTypeAttribute.g.verified.cs +++ /dev/null @@ -1,7 +0,0 @@ -//HintName: ValidatableTypeAttribute.g.cs -// -[global::Microsoft.CodeAnalysis.EmbeddedAttribute] -[global::System.AttributeUsage(global::System.AttributeTargets.Class)] -internal sealed class ValidatableTypeAttribute : global::System.Attribute -{ -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNoAddValidationCallExists#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNoAddValidationCallExists#EmbeddedAttribute.g.verified.cs deleted file mode 100644 index 03190c5b702f..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNoAddValidationCallExists#EmbeddedAttribute.g.verified.cs +++ /dev/null @@ -1,9 +0,0 @@ -//HintName: EmbeddedAttribute.g.cs -// -namespace Microsoft.CodeAnalysis -{ - [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class EmbeddedAttribute : global::System.Attribute - { - } -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNoAddValidationCallExists#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNoAddValidationCallExists#ValidatableTypeAttribute.g.verified.cs deleted file mode 100644 index 480eb60b7b9b..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNoAddValidationCallExists#ValidatableTypeAttribute.g.verified.cs +++ /dev/null @@ -1,7 +0,0 @@ -//HintName: ValidatableTypeAttribute.g.cs -// -[global::Microsoft.CodeAnalysis.EmbeddedAttribute] -[global::System.AttributeUsage(global::System.AttributeTargets.Class)] -internal sealed class ValidatableTypeAttribute : global::System.Attribute -{ -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNotCorrectAddValidationCallExists#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNotCorrectAddValidationCallExists#EmbeddedAttribute.g.verified.cs deleted file mode 100644 index 03190c5b702f..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNotCorrectAddValidationCallExists#EmbeddedAttribute.g.verified.cs +++ /dev/null @@ -1,9 +0,0 @@ -//HintName: EmbeddedAttribute.g.cs -// -namespace Microsoft.CodeAnalysis -{ - [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class EmbeddedAttribute : global::System.Attribute - { - } -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNotCorrectAddValidationCallExists#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNotCorrectAddValidationCallExists#ValidatableTypeAttribute.g.verified.cs deleted file mode 100644 index 480eb60b7b9b..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmitIfNotCorrectAddValidationCallExists#ValidatableTypeAttribute.g.verified.cs +++ /dev/null @@ -1,7 +0,0 @@ -//HintName: ValidatableTypeAttribute.g.cs -// -[global::Microsoft.CodeAnalysis.EmbeddedAttribute] -[global::System.AttributeUsage(global::System.AttributeTargets.Class)] -internal sealed class ValidatableTypeAttribute : global::System.Attribute -{ -} \ No newline at end of file From 44da579018b54f16ccf293210cc30b314c844563 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Tue, 5 Aug 2025 14:13:24 +0200 Subject: [PATCH 04/12] Cleanup --- .../ValidationsGenerator.AttributeParser.cs | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs b/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs index 6322e90da72a..6166819a7d09 100644 --- a/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs +++ b/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using System.Threading; using Microsoft.AspNetCore.App.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; @@ -41,9 +40,18 @@ internal static bool ShouldTransformSymbolWithValidatableTypeAttribute(SyntaxNod // Check if the type has any attribute that could be ValidatableTypeAttribute var typeDeclaration = (TypeDeclarationSyntax)syntaxNode; - return typeDeclaration.AttributeLists - .SelectMany(al => al.Attributes) - .Any(IsValidatableTypeAttribute); + foreach (var attributeList in typeDeclaration.AttributeLists) + { + foreach (var attribute in attributeList.Attributes) + { + if (IsValidatableTypeAttribute(attribute)) + { + return true; + } + } + } + + return false; } internal ImmutableArray TransformValidatableTypeWithValidatableTypeAttribute(GeneratorSyntaxContext context, CancellationToken cancellationToken) @@ -86,12 +94,12 @@ private static bool IsValidatableTypeAttribute(AttributeSyntax attribute) private static bool HasValidatableTypeAttribute(ITypeSymbol typeSymbol) { - return typeSymbol.GetAttributes().Any(attr => + foreach (var attr in typeSymbol.GetAttributes()) { var attributeClass = attr.AttributeClass; - if (attributeClass == null) + if (attributeClass is null) { - return false; + continue; } var name = attributeClass.Name; @@ -107,11 +115,16 @@ private static bool HasValidatableTypeAttribute(ITypeSymbol typeSymbol) if (name == "ValidatableTypeAttribute") { // Additional check: ensure it's marked with [Embedded] to confirm it's auto-generated - return attributeClass.GetAttributes().Any(embeddedAttr => - embeddedAttr.AttributeClass?.ToDisplayString() == "Microsoft.CodeAnalysis.EmbeddedAttribute"); + foreach (var embeddedAttr in attributeClass.GetAttributes()) + { + if (embeddedAttr.AttributeClass?.ToDisplayString() == "Microsoft.CodeAnalysis.EmbeddedAttribute") + { + return true; + } + } } + } - return false; - }); + return false; } } From 931f5cda7e425d42cd7e9523ea25989701649ab2 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Tue, 5 Aug 2025 19:49:56 +0200 Subject: [PATCH 05/12] tmp --- .../ValidationsGenerator.AttributeParser.cs | 29 ++++++++------ src/Validation/gen/ValidationsGenerator.cs | 40 ++++++++++++++++--- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs b/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs index 6166819a7d09..71c70615047c 100644 --- a/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs +++ b/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs @@ -25,12 +25,12 @@ internal ImmutableArray TransformValidatableTypeWithAttribute(G var wellKnownTypes = WellKnownTypes.GetOrCreate(context.SemanticModel.Compilation); if (TryExtractValidatableType((ITypeSymbol)context.TargetSymbol, wellKnownTypes, ref validatableTypes, ref visitedTypes)) { - return [..validatableTypes]; + return [.. validatableTypes]; } return []; } - internal static bool ShouldTransformSymbolWithValidatableTypeAttribute(SyntaxNode syntaxNode, CancellationToken _) + internal static bool ShouldTransformSymbolWithEmbeddedValidatableTypeAttribute(SyntaxNode syntaxNode, CancellationToken _) { // Only process class and record declarations if (syntaxNode is not (ClassDeclarationSyntax or RecordDeclarationSyntax)) @@ -54,7 +54,10 @@ internal static bool ShouldTransformSymbolWithValidatableTypeAttribute(SyntaxNod return false; } - internal ImmutableArray TransformValidatableTypeWithValidatableTypeAttribute(GeneratorSyntaxContext context, CancellationToken cancellationToken) + internal ImmutableArray TransformValidatableTypeWithEmbeddedValidatableTypeAttribute( + GeneratorSyntaxContext context, + GeneratorSettings settings, + CancellationToken cancellationToken) { if (context.Node is not (ClassDeclarationSyntax or RecordDeclarationSyntax)) { @@ -67,8 +70,9 @@ internal ImmutableArray TransformValidatableTypeWithValidatable return []; } - // Check if the type has a ValidatableTypeAttribute (framework or auto-generated) - if (!HasValidatableTypeAttribute(typeSymbol)) + var validatableTypeAttributeName = $"{settings.RootNamespace}.ValidatableTypeAttribute"; + // Check if the type has a ValidatableTypeAttribute (auto-generated) + if (!HasValidatableTypeAttribute(typeSymbol, validatableTypeAttributeName)) { return []; } @@ -79,7 +83,7 @@ internal ImmutableArray TransformValidatableTypeWithValidatable if (TryExtractValidatableType(typeSymbol, wellKnownTypes, ref validatableTypes, ref visitedTypes)) { - return [..validatableTypes]; + return [.. validatableTypes]; } return []; } @@ -92,7 +96,7 @@ private static bool IsValidatableTypeAttribute(AttributeSyntax attribute) name.EndsWith(".ValidatableTypeAttribute", StringComparison.Ordinal); } - private static bool HasValidatableTypeAttribute(ITypeSymbol typeSymbol) + private static bool HasValidatableTypeAttribute(ITypeSymbol typeSymbol, string validatableTypeAttributeName) { foreach (var attr in typeSymbol.GetAttributes()) { @@ -103,16 +107,15 @@ private static bool HasValidatableTypeAttribute(ITypeSymbol typeSymbol) } var name = attributeClass.Name; - var fullName = attributeClass.ToDisplayString(); - - // Check for framework attribute - if (fullName == "Microsoft.Extensions.Validation.ValidatableTypeAttribute") + var namespaceName = attributeClass.ContainingNamespace.ToDisplayString(); + var fullName = $"{namespaceName}.{name}"; + if (!string.Equals(fullName, validatableTypeAttributeName, StringComparison.Ordinal)) { - return true; + continue; } // Check for auto-generated attribute (any namespace) - if (name == "ValidatableTypeAttribute") + if (name == validatableTypeAttributeName) { // Additional check: ensure it's marked with [Embedded] to confirm it's auto-generated foreach (var embeddedAttr in attributeClass.GetAttributes()) diff --git a/src/Validation/gen/ValidationsGenerator.cs b/src/Validation/gen/ValidationsGenerator.cs index 58542ed0e48c..7acb4b327dde 100644 --- a/src/Validation/gen/ValidationsGenerator.cs +++ b/src/Validation/gen/ValidationsGenerator.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Immutable; using System.Linq; +using Microsoft.AspNetCore.App.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; namespace Microsoft.Extensions.Validation; @@ -33,23 +35,49 @@ public void Initialize(IncrementalGeneratorInitializationContext context) predicate: FindAddValidation, transform: TransformAddValidation ); + // Extract types that have been marked with [ValidatableType]. // This handles both the framework attribute and the auto-generated attribute. - var validatableTypesWithAttribute = context.SyntaxProvider.CreateSyntaxProvider( - predicate: ShouldTransformSymbolWithValidatableTypeAttribute, - transform: TransformValidatableTypeWithValidatableTypeAttribute - ).Where(types => types.Length > 0); + var validatableTypesWithAttribute = context.SyntaxProvider.ForAttributeWithMetadataName( + "Microsoft.Extensions.Validation.ValidatableTypeAttribute", + predicate: ShouldTransformSymbolWithAttribute, + transform: TransformValidatableTypeWithAttribute + ); + + // Extract types that have been marked with the embedded [ValidatableType]. + var generatedValidatableTypesWithAttribute = context.SyntaxProvider.CreateSyntaxProvider( + predicate: ShouldTransformSymbolWithEmbeddedValidatableTypeAttribute, + transform: (context, cancellation) => context + ).Combine(generatorSettings) + .Select((combined, cancellation) => + { + var (syntaxContext, settings) = combined; + if (!settings.ShouldGenerateAttribute || string.IsNullOrEmpty(settings.RootNamespace)) + { + return []; + } + + return TransformValidatableTypeWithEmbeddedValidatableTypeAttribute(syntaxContext, settings, cancellation); + }) + .Where(t => t.Length > 0); + + var allValidatableTypes = generatedValidatableTypesWithAttribute.Collect() + .Combine(validatableTypesWithAttribute.Collect()) + .SelectMany((pair, cancellation) => pair.Left.Concat(pair.Right).ToImmutableArray()); + // Extract all minimal API endpoints in the application. var endpoints = context.SyntaxProvider .CreateSyntaxProvider( predicate: FindEndpoints, transform: TransformEndpoints) .Where(endpoint => endpoint is not null); + // Extract validatable types from all endpoints. var validatableTypesFromEndpoints = endpoints .Select(ExtractValidatableEndpoint); + // Join all validatable types encountered in the type graph. - var validatableTypes = validatableTypesWithAttribute + var validatableTypes = allValidatableTypes .Concat(validatableTypesFromEndpoints) .Distinct(ValidatableTypeComparer.Instance) .Collect(); @@ -99,5 +127,5 @@ internal sealed class ValidatableTypeAttribute : global::System.Attribute context.AddSource("ValidatableTypeAttribute.g.cs", validatableTypeAttributeSource); } - private readonly record struct GeneratorSettings(string? RootNamespace, bool ShouldGenerateAttribute); + internal readonly record struct GeneratorSettings(string? RootNamespace, bool ShouldGenerateAttribute); } From 1f49d1a7a069a2d0f11c98605dde3215c6f51dea Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Wed, 6 Aug 2025 11:56:54 +0200 Subject: [PATCH 06/12] tmp --- .../ValidationsGenerator.AttributeParser.cs | 101 ----------------- src/Validation/gen/ValidationsGenerator.cs | 92 +++------------- ...dationsGenerator.AutoGeneratedAttribute.cs | 102 ++++++++++++++++-- 3 files changed, 108 insertions(+), 187 deletions(-) diff --git a/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs b/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs index 71c70615047c..fa99c133dbbc 100644 --- a/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs +++ b/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs @@ -29,105 +29,4 @@ internal ImmutableArray TransformValidatableTypeWithAttribute(G } return []; } - - internal static bool ShouldTransformSymbolWithEmbeddedValidatableTypeAttribute(SyntaxNode syntaxNode, CancellationToken _) - { - // Only process class and record declarations - if (syntaxNode is not (ClassDeclarationSyntax or RecordDeclarationSyntax)) - { - return false; - } - - // Check if the type has any attribute that could be ValidatableTypeAttribute - var typeDeclaration = (TypeDeclarationSyntax)syntaxNode; - foreach (var attributeList in typeDeclaration.AttributeLists) - { - foreach (var attribute in attributeList.Attributes) - { - if (IsValidatableTypeAttribute(attribute)) - { - return true; - } - } - } - - return false; - } - - internal ImmutableArray TransformValidatableTypeWithEmbeddedValidatableTypeAttribute( - GeneratorSyntaxContext context, - GeneratorSettings settings, - CancellationToken cancellationToken) - { - if (context.Node is not (ClassDeclarationSyntax or RecordDeclarationSyntax)) - { - return []; - } - - var typeSymbol = context.SemanticModel.GetDeclaredSymbol(context.Node, cancellationToken) as ITypeSymbol; - if (typeSymbol == null) - { - return []; - } - - var validatableTypeAttributeName = $"{settings.RootNamespace}.ValidatableTypeAttribute"; - // Check if the type has a ValidatableTypeAttribute (auto-generated) - if (!HasValidatableTypeAttribute(typeSymbol, validatableTypeAttributeName)) - { - return []; - } - - var validatableTypes = new HashSet(ValidatableTypeComparer.Instance); - List visitedTypes = []; - var wellKnownTypes = WellKnownTypes.GetOrCreate(context.SemanticModel.Compilation); - - if (TryExtractValidatableType(typeSymbol, wellKnownTypes, ref validatableTypes, ref visitedTypes)) - { - return [.. validatableTypes]; - } - return []; - } - - private static bool IsValidatableTypeAttribute(AttributeSyntax attribute) - { - var name = attribute.Name.ToString(); - return name is "ValidatableType" or "ValidatableTypeAttribute" || - name.EndsWith(".ValidatableType", StringComparison.Ordinal) || - name.EndsWith(".ValidatableTypeAttribute", StringComparison.Ordinal); - } - - private static bool HasValidatableTypeAttribute(ITypeSymbol typeSymbol, string validatableTypeAttributeName) - { - foreach (var attr in typeSymbol.GetAttributes()) - { - var attributeClass = attr.AttributeClass; - if (attributeClass is null) - { - continue; - } - - var name = attributeClass.Name; - var namespaceName = attributeClass.ContainingNamespace.ToDisplayString(); - var fullName = $"{namespaceName}.{name}"; - if (!string.Equals(fullName, validatableTypeAttributeName, StringComparison.Ordinal)) - { - continue; - } - - // Check for auto-generated attribute (any namespace) - if (name == validatableTypeAttributeName) - { - // Additional check: ensure it's marked with [Embedded] to confirm it's auto-generated - foreach (var embeddedAttr in attributeClass.GetAttributes()) - { - if (embeddedAttr.AttributeClass?.ToDisplayString() == "Microsoft.CodeAnalysis.EmbeddedAttribute") - { - return true; - } - } - } - } - - return false; - } } diff --git a/src/Validation/gen/ValidationsGenerator.cs b/src/Validation/gen/ValidationsGenerator.cs index 7acb4b327dde..ea72b31e64b9 100644 --- a/src/Validation/gen/ValidationsGenerator.cs +++ b/src/Validation/gen/ValidationsGenerator.cs @@ -14,56 +14,31 @@ public sealed partial class ValidationsGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { - // Get the MSBuild properties for controlling attribute generation - var generatorSettings = context.AnalyzerConfigOptionsProvider - .Select((configOptions, _) => - { - configOptions.GlobalOptions.TryGetValue("build_property.RootNamespace", out var rootNamespace); - configOptions.GlobalOptions.TryGetValue("build_property.GenerateBuiltinValidatableTypeAttribute", out var generateAttribute); - - var shouldGenerate = !string.IsNullOrEmpty(rootNamespace) && - string.Equals(generateAttribute, "true", StringComparison.OrdinalIgnoreCase); - - return new GeneratorSettings(rootNamespace, shouldGenerate); - }); - - // Emit the ValidatableTypeAttribute conditionally - context.RegisterSourceOutput(generatorSettings, EmitValidatableTypeAttribute); - // Find the builder.Services.AddValidation() call in the application. var addValidation = context.SyntaxProvider.CreateSyntaxProvider( predicate: FindAddValidation, transform: TransformAddValidation ); - // Extract types that have been marked with [ValidatableType]. - // This handles both the framework attribute and the auto-generated attribute. - var validatableTypesWithAttribute = context.SyntaxProvider.ForAttributeWithMetadataName( + // Extract types that have been marked with framework [ValidatableType]. + var frameworkValidatableTypes = context.SyntaxProvider.ForAttributeWithMetadataName( "Microsoft.Extensions.Validation.ValidatableTypeAttribute", predicate: ShouldTransformSymbolWithAttribute, transform: TransformValidatableTypeWithAttribute ); - // Extract types that have been marked with the embedded [ValidatableType]. - var generatedValidatableTypesWithAttribute = context.SyntaxProvider.CreateSyntaxProvider( - predicate: ShouldTransformSymbolWithEmbeddedValidatableTypeAttribute, - transform: (context, cancellation) => context - ).Combine(generatorSettings) - .Select((combined, cancellation) => - { - var (syntaxContext, settings) = combined; - if (!settings.ShouldGenerateAttribute || string.IsNullOrEmpty(settings.RootNamespace)) - { - return []; - } - - return TransformValidatableTypeWithEmbeddedValidatableTypeAttribute(syntaxContext, settings, cancellation); - }) - .Where(t => t.Length > 0); + // Extract types that have been marked with generated [ValidatableType]. + var generatedValidatableTypes = context.SyntaxProvider.ForAttributeWithMetadataName( + "Microsoft.Extensions.Validation.Generated.ValidatableTypeAttribute", + predicate: ShouldTransformSymbolWithAttribute, + transform: TransformValidatableTypeWithAttribute + ); - var allValidatableTypes = generatedValidatableTypesWithAttribute.Collect() - .Combine(validatableTypesWithAttribute.Collect()) - .SelectMany((pair, cancellation) => pair.Left.Concat(pair.Right).ToImmutableArray()); + // Combine both sources of validatable types + var validatableTypesWithAttribute = frameworkValidatableTypes + .Collect() + .Combine(generatedValidatableTypes.Collect()) + .SelectMany((pair, _) => pair.Left.Concat(pair.Right).ToImmutableArray()); // Extract all minimal API endpoints in the application. var endpoints = context.SyntaxProvider @@ -77,7 +52,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Select(ExtractValidatableEndpoint); // Join all validatable types encountered in the type graph. - var validatableTypes = allValidatableTypes + var validatableTypes = validatableTypesWithAttribute .Concat(validatableTypesFromEndpoints) .Distinct(ValidatableTypeComparer.Instance) .Collect(); @@ -89,43 +64,4 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // ValidatableTypeInfo for all validatable types. context.RegisterSourceOutput(emitInputs, Emit); } - - private static void EmitValidatableTypeAttribute(SourceProductionContext context, GeneratorSettings settings) - { - // Only emit if the build property is set to true and root namespace is provided - if (!settings.ShouldGenerateAttribute) - { - return; - } - - // First, emit the EmbeddedAttribute if it doesn't exist - var embeddedAttributeSource = """ - // - namespace Microsoft.CodeAnalysis - { - [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class EmbeddedAttribute : global::System.Attribute - { - } - } - """; - - context.AddSource("EmbeddedAttribute.g.cs", embeddedAttributeSource); - - // Generate the ValidatableTypeAttribute in the project's root namespace - var validatableTypeAttributeSource = $$""" - // - namespace {{settings.RootNamespace}}; - - [global::Microsoft.CodeAnalysis.EmbeddedAttribute] - [global::System.AttributeUsage(global::System.AttributeTargets.Class)] - internal sealed class ValidatableTypeAttribute : global::System.Attribute - { - } - """; - - context.AddSource("ValidatableTypeAttribute.g.cs", validatableTypeAttributeSource); - } - - internal readonly record struct GeneratorSettings(string? RootNamespace, bool ShouldGenerateAttribute); } diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs index 7321e82a4102..1444f7626de8 100644 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs @@ -11,19 +11,18 @@ namespace Microsoft.Extensions.Validation.GeneratorTests; public partial class ValidationsGeneratorTests : ValidationsGeneratorTestBase { [Fact] - public async Task CanUseAutoGeneratedValidatableTypeAttribute() + public async Task CanDiscoverGeneratedValidatableTypeAttribute() { var source = """ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using System.ComponentModel.DataAnnotations; + using Microsoft.Extensions.Validation.Generated; var builder = WebApplication.CreateBuilder(); builder.Services.AddValidation(); var app = builder.Build(); - // Using the auto-generated ValidatableTypeAttribute - // No manual attribute definition needed! [ValidatableType] public class Customer { @@ -39,10 +38,97 @@ public class Customer app.Run(); """; - await Verify(source, out var compilation, globalOptions: new Dictionary - { - ["build_property.RootNamespace"] = "TestApp", - ["build_property.GenerateBuiltinValidatableTypeAttribute"] = "true" - }); + // Simulate the Razor SDK generating the attribute + var generatedAttributeSource = """ + // + namespace Microsoft.CodeAnalysis + { + [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class EmbeddedAttribute : global::System.Attribute + { + } + } + + namespace Microsoft.Extensions.Validation.Generated; + + [global::Microsoft.CodeAnalysis.EmbeddedAttribute] + [global::System.AttributeUsage(global::System.AttributeTargets.Class)] + internal sealed class ValidatableTypeAttribute : global::System.Attribute + { + } + """; + + // Combine the generated attribute with the user's source + var combinedSource = generatedAttributeSource + "\n" + source; + + await Verify(combinedSource, out var compilation); + } + + [Fact] + public async Task CanUseBothFrameworkAndGeneratedValidatableTypeAttributes() + { + var source = """ + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using System.ComponentModel.DataAnnotations; + using Microsoft.Extensions.Validation; + using Microsoft.Extensions.Validation.Generated; + + var builder = WebApplication.CreateBuilder(); + builder.Services.AddValidation(); + var app = builder.Build(); + + // Using framework attribute + [Microsoft.Extensions.Validation.ValidatableType] + public class Customer + { + [Required] + public string Name { get; set; } = ""; + + [EmailAddress] + public string Email { get; set; } = ""; + } + + // Using generated attribute + [ValidatableType] + public class Product + { + [Required] + public string ProductName { get; set; } = ""; + + [Range(0, double.MaxValue)] + public decimal Price { get; set; } + } + + app.MapPost("/customers", (Customer customer) => "OK"); + app.MapPost("/products", (Product product) => "OK"); + + app.Run(); + """; + + // Simulate the Razor SDK generating the attribute + var generatedAttributeSource = """ + // + namespace Microsoft.CodeAnalysis + { + [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class EmbeddedAttribute : global::System.Attribute + { + } + } + + namespace Microsoft.Extensions.Validation.Generated; + + [global::Microsoft.CodeAnalysis.EmbeddedAttribute] + [global::System.AttributeUsage(global::System.AttributeTargets.Class)] + internal sealed class ValidatableTypeAttribute : global::System.Attribute + { + } + """; + + // Combine the generated attribute with the user's source + var combinedSource = generatedAttributeSource + "\n" + source; + + await Verify(combinedSource, out var compilation); } } From b90dd1b645556ee2e5eafa03ec1934e36eff41e0 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Wed, 6 Aug 2025 12:06:51 +0200 Subject: [PATCH 07/12] cleanup --- ...xtensions.Validation.GeneratorTests.csproj | 2 - .../ValidationsGeneratorTestBase.cs | 52 +------------------ 2 files changed, 1 insertion(+), 53 deletions(-) diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/Microsoft.Extensions.Validation.GeneratorTests.csproj b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/Microsoft.Extensions.Validation.GeneratorTests.csproj index 5af4c2ccda9b..db08d20e21a2 100644 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/Microsoft.Extensions.Validation.GeneratorTests.csproj +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/Microsoft.Extensions.Validation.GeneratorTests.csproj @@ -28,8 +28,6 @@ - - diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGeneratorTestBase.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGeneratorTestBase.cs index 3af13a1d2fc9..81500f090c96 100644 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGeneratorTestBase.cs +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGeneratorTestBase.cs @@ -1,5 +1,4 @@ #pragma warning disable ASP0029 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. -#nullable disable // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. @@ -23,7 +22,6 @@ using Microsoft.AspNetCore.Http.Features.Authentication; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Text; using Microsoft.Extensions.DependencyInjection; @@ -45,9 +43,6 @@ public partial class ValidationsGeneratorTestBase : LoggedTestBase .WithFeatures([new KeyValuePair("InterceptorsNamespaces", "Microsoft.Extensions.Validation.Generated")]); internal static Task Verify(string source, out Compilation compilation) - => Verify(source, out compilation, globalOptions: null); - - internal static Task Verify(string source, out Compilation compilation, Dictionary globalOptions) { var references = AppDomain.CurrentDomain.GetAssemblies() .Where(assembly => !assembly.IsDynamic && !string.IsNullOrWhiteSpace(assembly.Location)) @@ -82,21 +77,7 @@ internal static Task Verify(string source, out Compilation compilation, Dictiona references, new CSharpCompilationOptions(OutputKind.ConsoleApplication)); var generator = new ValidationsGenerator(); - - // Create the driver with optional global options - CSharpGeneratorDriver driver; - if (globalOptions != null) - { - var analyzerConfig = new InMemoryAnalyzerConfigOptionsProvider(globalOptions); - driver = CSharpGeneratorDriver.Create( - generators: [generator.AsSourceGenerator()], - parseOptions: ParseOptions, - optionsProvider: analyzerConfig); - } - else - { - driver = CSharpGeneratorDriver.Create(generators: [generator.AsSourceGenerator()], parseOptions: ParseOptions); - } + var driver = CSharpGeneratorDriver.Create(generators: [generator.AsSourceGenerator()], parseOptions: ParseOptions); return Verifier .Verify(driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out compilation, out var diagnostics)) .ScrubLinesWithReplace(line => InterceptsLocationRegex().Replace(line, "[InterceptsLocation]")) @@ -622,35 +603,4 @@ public RequestBodyDetectionFeature(bool canHaveBody) public bool CanHaveBody { get; } } - - internal sealed class InMemoryAnalyzerConfigOptionsProvider : AnalyzerConfigOptionsProvider - { - private readonly InMemoryAnalyzerConfigOptions _globalOptions; - - public InMemoryAnalyzerConfigOptionsProvider(Dictionary globalOptions) - { - _globalOptions = new InMemoryAnalyzerConfigOptions(globalOptions); - } - - public override AnalyzerConfigOptions GlobalOptions => _globalOptions; - - public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => _globalOptions; - - public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => _globalOptions; - } - - internal sealed class InMemoryAnalyzerConfigOptions : AnalyzerConfigOptions - { - private readonly Dictionary _options; - - public InMemoryAnalyzerConfigOptions(Dictionary options) - { - _options = options; - } - - public override bool TryGetValue(string key, out string value) - { - return _options.TryGetValue(key, out value!); - } - } } From e56c5dfbb9796d48f3b549aa46173adcae99cc5b Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Wed, 6 Aug 2025 12:30:46 +0200 Subject: [PATCH 08/12] Rename --- src/Validation/gen/ValidationsGenerator.cs | 2 +- .../ValidationsGenerator.AutoGeneratedAttribute.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Validation/gen/ValidationsGenerator.cs b/src/Validation/gen/ValidationsGenerator.cs index ea72b31e64b9..5e40394b50e9 100644 --- a/src/Validation/gen/ValidationsGenerator.cs +++ b/src/Validation/gen/ValidationsGenerator.cs @@ -29,7 +29,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // Extract types that have been marked with generated [ValidatableType]. var generatedValidatableTypes = context.SyntaxProvider.ForAttributeWithMetadataName( - "Microsoft.Extensions.Validation.Generated.ValidatableTypeAttribute", + "Microsoft.Extensions.Validation.Embedded.ValidatableTypeAttribute", predicate: ShouldTransformSymbolWithAttribute, transform: TransformValidatableTypeWithAttribute ); diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs index 1444f7626de8..4a3759846923 100644 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs @@ -17,7 +17,7 @@ public async Task CanDiscoverGeneratedValidatableTypeAttribute() using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using System.ComponentModel.DataAnnotations; - using Microsoft.Extensions.Validation.Generated; + using Microsoft.Extensions.Validation.Embedded; var builder = WebApplication.CreateBuilder(); builder.Services.AddValidation(); @@ -49,7 +49,7 @@ internal sealed class EmbeddedAttribute : global::System.Attribute } } - namespace Microsoft.Extensions.Validation.Generated; + namespace Microsoft.Extensions.Validation.Embedded; [global::Microsoft.CodeAnalysis.EmbeddedAttribute] [global::System.AttributeUsage(global::System.AttributeTargets.Class)] @@ -72,7 +72,7 @@ public async Task CanUseBothFrameworkAndGeneratedValidatableTypeAttributes() using Microsoft.Extensions.DependencyInjection; using System.ComponentModel.DataAnnotations; using Microsoft.Extensions.Validation; - using Microsoft.Extensions.Validation.Generated; + using Microsoft.Extensions.Validation.Embedded; var builder = WebApplication.CreateBuilder(); builder.Services.AddValidation(); @@ -117,7 +117,7 @@ internal sealed class EmbeddedAttribute : global::System.Attribute } } - namespace Microsoft.Extensions.Validation.Generated; + namespace Microsoft.Extensions.Validation.Embedded; [global::Microsoft.CodeAnalysis.EmbeddedAttribute] [global::System.AttributeUsage(global::System.AttributeTargets.Class)] From 5899165103e49fc1485c9594a38925b504ec4ede Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Thu, 7 Aug 2025 00:49:57 +0200 Subject: [PATCH 09/12] Address feedback --- .../ValidationsGenerator.AttributeParser.cs | 6 +- src/Validation/gen/ValidationsGenerator.cs | 33 +++++++--- ...dationsGenerator.AutoGeneratedAttribute.cs | 63 +++++++------------ 3 files changed, 49 insertions(+), 53 deletions(-) diff --git a/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs b/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs index fa99c133dbbc..23d7474ee63b 100644 --- a/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs +++ b/src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs @@ -1,7 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. - -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -13,12 +11,12 @@ namespace Microsoft.Extensions.Validation; public sealed partial class ValidationsGenerator : IIncrementalGenerator { - internal static bool ShouldTransformSymbolWithAttribute(SyntaxNode syntaxNode, CancellationToken _) + internal static bool ShouldTransformSymbolWithAttribute(SyntaxNode syntaxNode, CancellationToken cancellationToken) { return syntaxNode is ClassDeclarationSyntax or RecordDeclarationSyntax; } - internal ImmutableArray TransformValidatableTypeWithAttribute(GeneratorAttributeSyntaxContext context, CancellationToken _) + internal ImmutableArray TransformValidatableTypeWithAttribute(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) { var validatableTypes = new HashSet(ValidatableTypeComparer.Instance); List visitedTypes = []; diff --git a/src/Validation/gen/ValidationsGenerator.cs b/src/Validation/gen/ValidationsGenerator.cs index 5e40394b50e9..9423965c1f7c 100644 --- a/src/Validation/gen/ValidationsGenerator.cs +++ b/src/Validation/gen/ValidationsGenerator.cs @@ -35,10 +35,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) ); // Combine both sources of validatable types - var validatableTypesWithAttribute = frameworkValidatableTypes - .Collect() - .Combine(generatedValidatableTypes.Collect()) - .SelectMany((pair, _) => pair.Left.Concat(pair.Right).ToImmutableArray()); + var validatableTypesWithAttribute = frameworkValidatableTypes.Concat(generatedValidatableTypes); // Extract all minimal API endpoints in the application. var endpoints = context.SyntaxProvider @@ -52,8 +49,29 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Select(ExtractValidatableEndpoint); // Join all validatable types encountered in the type graph. - var validatableTypes = validatableTypesWithAttribute - .Concat(validatableTypesFromEndpoints) + var allValidatableTypesProviders = validatableTypesWithAttribute + .Collect() + .Combine(validatableTypesFromEndpoints.Collect()) + .SelectMany(static (tuple, _) => + { + var results = ImmutableArray.CreateBuilder(); + + // Add from attribute-based sources + foreach (var array in tuple.Left) + { + results.AddRange(array); + } + + // Add from endpoint sources + foreach (var array in tuple.Right) + { + results.AddRange(array); + } + + return results.DrainToImmutable(); + }); + + var validatableTypes = allValidatableTypesProviders .Distinct(ValidatableTypeComparer.Instance) .Collect(); @@ -62,6 +80,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // Emit the IValidatableInfo resolver injection and // ValidatableTypeInfo for all validatable types. - context.RegisterSourceOutput(emitInputs, Emit); + context.RegisterSourceOutput(emitInputs, (context, emitInputs) => + Emit(context, (emitInputs.Left, emitInputs.Right))); } } diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs index 4a3759846923..c5cb94fb15c4 100644 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs @@ -10,6 +10,25 @@ namespace Microsoft.Extensions.Validation.GeneratorTests; [UsesVerify] public partial class ValidationsGeneratorTests : ValidationsGeneratorTestBase { + private const string GeneratedAttributeSource = """ + // + namespace Microsoft.CodeAnalysis + { + [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class EmbeddedAttribute : global::System.Attribute + { + } + } + + namespace Microsoft.Extensions.Validation.Embedded; + + [global::Microsoft.CodeAnalysis.EmbeddedAttribute] + [global::System.AttributeUsage(global::System.AttributeTargets.Class)] + internal sealed class ValidatableTypeAttribute : global::System.Attribute + { + } + """; + [Fact] public async Task CanDiscoverGeneratedValidatableTypeAttribute() { @@ -38,28 +57,8 @@ public class Customer app.Run(); """; - // Simulate the Razor SDK generating the attribute - var generatedAttributeSource = """ - // - namespace Microsoft.CodeAnalysis - { - [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class EmbeddedAttribute : global::System.Attribute - { - } - } - - namespace Microsoft.Extensions.Validation.Embedded; - - [global::Microsoft.CodeAnalysis.EmbeddedAttribute] - [global::System.AttributeUsage(global::System.AttributeTargets.Class)] - internal sealed class ValidatableTypeAttribute : global::System.Attribute - { - } - """; - // Combine the generated attribute with the user's source - var combinedSource = generatedAttributeSource + "\n" + source; + var combinedSource = GeneratedAttributeSource + "\n" + source; await Verify(combinedSource, out var compilation); } @@ -106,28 +105,8 @@ public class Product app.Run(); """; - // Simulate the Razor SDK generating the attribute - var generatedAttributeSource = """ - // - namespace Microsoft.CodeAnalysis - { - [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class EmbeddedAttribute : global::System.Attribute - { - } - } - - namespace Microsoft.Extensions.Validation.Embedded; - - [global::Microsoft.CodeAnalysis.EmbeddedAttribute] - [global::System.AttributeUsage(global::System.AttributeTargets.Class)] - internal sealed class ValidatableTypeAttribute : global::System.Attribute - { - } - """; - // Combine the generated attribute with the user's source - var combinedSource = generatedAttributeSource + "\n" + source; + var combinedSource = GeneratedAttributeSource + "\n" + source; await Verify(combinedSource, out var compilation); } From 03f3d8bb75838c7eaeaa5e23991a63c5972b9a8b Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Thu, 7 Aug 2025 12:36:43 +0200 Subject: [PATCH 10/12] Cleanup From e0b016e73fb6998f018dc8f6693eb0c47dd01196 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Thu, 7 Aug 2025 18:26:38 +0200 Subject: [PATCH 11/12] Fixes --- .../IncrementalValuesProviderExtensions.cs | 26 +++++++++++++++++++ src/Validation/gen/ValidationsGenerator.cs | 24 +++-------------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/Validation/gen/Extensions/IncrementalValuesProviderExtensions.cs b/src/Validation/gen/Extensions/IncrementalValuesProviderExtensions.cs index aea74a7ab19c..45e30b2efcd1 100644 --- a/src/Validation/gen/Extensions/IncrementalValuesProviderExtensions.cs +++ b/src/Validation/gen/Extensions/IncrementalValuesProviderExtensions.cs @@ -63,6 +63,32 @@ public static IncrementalValuesProvider Concat( }); } + public static IncrementalValuesProvider Concat( + this IncrementalValuesProvider> first, + IncrementalValuesProvider second) + { + return first.Collect() + .Combine(second.Collect()) + .SelectMany((tuple, _) => + { + if (tuple.Left.IsEmpty) + { + return tuple.Right; + } + + var results = ImmutableArray.CreateBuilder(tuple.Left.Length + tuple.Right.Length); + for (var i = 0; i < tuple.Left.Length; i++) + { + results.AddRange(tuple.Left[i]); + } + for (var i = 0; i < tuple.Right.Length; i++) + { + results.AddRange(tuple.Right[i]); + } + return results.DrainToImmutable(); + }); + } + private sealed class ImmutableArrayEqualityComparer : IEqualityComparer> { public static readonly ImmutableArrayEqualityComparer Instance = new(); diff --git a/src/Validation/gen/ValidationsGenerator.cs b/src/Validation/gen/ValidationsGenerator.cs index 9423965c1f7c..512f1a37f61b 100644 --- a/src/Validation/gen/ValidationsGenerator.cs +++ b/src/Validation/gen/ValidationsGenerator.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Immutable; using System.Linq; +using System.Runtime.InteropServices; using Microsoft.AspNetCore.App.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; @@ -49,27 +50,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Select(ExtractValidatableEndpoint); // Join all validatable types encountered in the type graph. - var allValidatableTypesProviders = validatableTypesWithAttribute - .Collect() - .Combine(validatableTypesFromEndpoints.Collect()) - .SelectMany(static (tuple, _) => - { - var results = ImmutableArray.CreateBuilder(); - - // Add from attribute-based sources - foreach (var array in tuple.Left) - { - results.AddRange(array); - } - - // Add from endpoint sources - foreach (var array in tuple.Right) - { - results.AddRange(array); - } - - return results.DrainToImmutable(); - }); + var allValidatableTypesProviders = validatableTypesFromEndpoints + .Concat(validatableTypesWithAttribute); var validatableTypes = allValidatableTypesProviders .Distinct(ValidatableTypeComparer.Instance) From c887ba40148d58563624edf13b5fda1952ed23e5 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Thu, 7 Aug 2025 22:54:52 +0200 Subject: [PATCH 12/12] Fix tests --- ...dationsGenerator.AutoGeneratedAttribute.cs | 130 ++++++------ ...ute#ValidatableInfoResolver.g.verified.cs} | 8 +- ...eAttribute#EmbeddedAttribute.g.verified.cs | 9 - ...ute#ValidatableTypeAttribute.g.verified.cs | 9 - ...utes#ValidatableInfoResolver.g.verified.cs | 191 ++++++++++++++++++ 5 files changed, 267 insertions(+), 80 deletions(-) rename src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/{ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableInfoResolver.g.verified.cs => ValidationsGeneratorTests.CanDiscoverGeneratedValidatableTypeAttribute#ValidatableInfoResolver.g.verified.cs} (97%) delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#EmbeddedAttribute.g.verified.cs delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableTypeAttribute.g.verified.cs create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseBothFrameworkAndGeneratedValidatableTypeAttributes#ValidatableInfoResolver.g.verified.cs diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs index c5cb94fb15c4..788d488e49b1 100644 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs @@ -20,12 +20,13 @@ internal sealed class EmbeddedAttribute : global::System.Attribute } } - namespace Microsoft.Extensions.Validation.Embedded; - - [global::Microsoft.CodeAnalysis.EmbeddedAttribute] - [global::System.AttributeUsage(global::System.AttributeTargets.Class)] - internal sealed class ValidatableTypeAttribute : global::System.Attribute + namespace Microsoft.Extensions.Validation.Embedded { + [global::Microsoft.CodeAnalysis.EmbeddedAttribute] + [global::System.AttributeUsage(global::System.AttributeTargets.Class)] + internal sealed class ValidatableTypeAttribute : global::System.Attribute + { + } } """; @@ -33,28 +34,36 @@ internal sealed class ValidatableTypeAttribute : global::System.Attribute public async Task CanDiscoverGeneratedValidatableTypeAttribute() { var source = """ - using Microsoft.AspNetCore.Builder; - using Microsoft.Extensions.DependencyInjection; - using System.ComponentModel.DataAnnotations; - using Microsoft.Extensions.Validation.Embedded; - - var builder = WebApplication.CreateBuilder(); - builder.Services.AddValidation(); - var app = builder.Build(); - [ValidatableType] - public class Customer + namespace MyApp { - [Required] - public string Name { get; set; } = ""; - - [EmailAddress] - public string Email { get; set; } = ""; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using System.ComponentModel.DataAnnotations; + using Microsoft.Extensions.Validation.Embedded; + + public class Program + { + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + builder.Services.AddValidation(); + var app = builder.Build(); + app.MapPost("/customers", (Customer customer) => "OK"); + app.Run(); + } + } + + [ValidatableType] + public class Customer + { + [Required] + public string Name { get; set; } = ""; + + [EmailAddress] + public string Email { get; set; } = ""; + } } - - app.MapPost("/customers", (Customer customer) => "OK"); - - app.Run(); """; // Combine the generated attribute with the user's source @@ -67,42 +76,47 @@ public class Customer public async Task CanUseBothFrameworkAndGeneratedValidatableTypeAttributes() { var source = """ - using Microsoft.AspNetCore.Builder; - using Microsoft.Extensions.DependencyInjection; - using System.ComponentModel.DataAnnotations; - using Microsoft.Extensions.Validation; - using Microsoft.Extensions.Validation.Embedded; - - var builder = WebApplication.CreateBuilder(); - builder.Services.AddValidation(); - var app = builder.Build(); - - // Using framework attribute - [Microsoft.Extensions.Validation.ValidatableType] - public class Customer - { - [Required] - public string Name { get; set; } = ""; - - [EmailAddress] - public string Email { get; set; } = ""; - } - - // Using generated attribute - [ValidatableType] - public class Product + namespace MyApp { - [Required] - public string ProductName { get; set; } = ""; - - [Range(0, double.MaxValue)] - public decimal Price { get; set; } + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using System.ComponentModel.DataAnnotations; + using Microsoft.Extensions.Validation.Embedded; + + public class Program + { + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + builder.Services.AddValidation(); + var app = builder.Build(); + app.MapPost("/customers", (Customer customer) => "OK"); + app.Run(); + } + } + + // Using framework attribute + [Microsoft.Extensions.Validation.ValidatableType] + public class Customer + { + [Required] + public string Name { get; set; } = ""; + + [EmailAddress] + public string Email { get; set; } = ""; + } + + // Using generated attribute + [ValidatableType] + public class Product + { + [Required] + public string ProductName { get; set; } = ""; + + [Range(0, double.MaxValue)] + public decimal Price { get; set; } + } } - - app.MapPost("/customers", (Customer customer) => "OK"); - app.MapPost("/products", (Product product) => "OK"); - - app.Run(); """; // Combine the generated attribute with the user's source diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableInfoResolver.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanDiscoverGeneratedValidatableTypeAttribute#ValidatableInfoResolver.g.verified.cs similarity index 97% rename from src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableInfoResolver.g.verified.cs rename to src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanDiscoverGeneratedValidatableTypeAttribute#ValidatableInfoResolver.g.verified.cs index 0ba2323165fc..226fc69f7d3a 100644 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableInfoResolver.g.verified.cs +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanDiscoverGeneratedValidatableTypeAttribute#ValidatableInfoResolver.g.verified.cs @@ -62,19 +62,19 @@ public GeneratedValidatableTypeInfo( public bool TryGetValidatableTypeInfo(global::System.Type type, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo) { validatableInfo = null; - if (type == typeof(global::Customer)) + if (type == typeof(global::MyApp.Customer)) { validatableInfo = new GeneratedValidatableTypeInfo( - type: typeof(global::Customer), + type: typeof(global::MyApp.Customer), members: [ new GeneratedValidatablePropertyInfo( - containingType: typeof(global::Customer), + containingType: typeof(global::MyApp.Customer), propertyType: typeof(string), name: "Name", displayName: "Name" ), new GeneratedValidatablePropertyInfo( - containingType: typeof(global::Customer), + containingType: typeof(global::MyApp.Customer), propertyType: typeof(string), name: "Email", displayName: "Email" diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#EmbeddedAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#EmbeddedAttribute.g.verified.cs deleted file mode 100644 index 03190c5b702f..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#EmbeddedAttribute.g.verified.cs +++ /dev/null @@ -1,9 +0,0 @@ -//HintName: EmbeddedAttribute.g.cs -// -namespace Microsoft.CodeAnalysis -{ - [global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class EmbeddedAttribute : global::System.Attribute - { - } -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableTypeAttribute.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableTypeAttribute.g.verified.cs deleted file mode 100644 index 7a46d42ccf31..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseAutoGeneratedValidatableTypeAttribute#ValidatableTypeAttribute.g.verified.cs +++ /dev/null @@ -1,9 +0,0 @@ -//HintName: ValidatableTypeAttribute.g.cs -// -namespace TestApp; - -[global::Microsoft.CodeAnalysis.EmbeddedAttribute] -[global::System.AttributeUsage(global::System.AttributeTargets.Class)] -internal sealed class ValidatableTypeAttribute : global::System.Attribute -{ -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseBothFrameworkAndGeneratedValidatableTypeAttributes#ValidatableInfoResolver.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseBothFrameworkAndGeneratedValidatableTypeAttributes#ValidatableInfoResolver.g.verified.cs new file mode 100644 index 000000000000..20a3fe742bd2 --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanUseBothFrameworkAndGeneratedValidatableTypeAttributes#ValidatableInfoResolver.g.verified.cs @@ -0,0 +1,191 @@ +//HintName: ValidatableInfoResolver.g.cs +#nullable enable annotations +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +#nullable enable +#pragma warning disable ASP0029 + +namespace System.Runtime.CompilerServices +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : System.Attribute + { + public InterceptsLocationAttribute(int version, string data) + { + } + } +} + +namespace Microsoft.Extensions.Validation.Generated +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file sealed class GeneratedValidatablePropertyInfo : global::Microsoft.Extensions.Validation.ValidatablePropertyInfo + { + public GeneratedValidatablePropertyInfo( + [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + global::System.Type containingType, + global::System.Type propertyType, + string name, + string displayName) : base(containingType, propertyType, name, displayName) + { + ContainingType = containingType; + Name = name; + } + + [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + internal global::System.Type ContainingType { get; } + internal string Name { get; } + + protected override global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes() + => ValidationAttributeCache.GetValidationAttributes(ContainingType, Name); + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file sealed class GeneratedValidatableTypeInfo : global::Microsoft.Extensions.Validation.ValidatableTypeInfo + { + public GeneratedValidatableTypeInfo( + [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.Interfaces)] + global::System.Type type, + ValidatablePropertyInfo[] members) : base(type, members) { } + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file class GeneratedValidatableInfoResolver : global::Microsoft.Extensions.Validation.IValidatableInfoResolver + { + public bool TryGetValidatableTypeInfo(global::System.Type type, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo) + { + validatableInfo = null; + if (type == typeof(global::MyApp.Customer)) + { + validatableInfo = new GeneratedValidatableTypeInfo( + type: typeof(global::MyApp.Customer), + members: [ + new GeneratedValidatablePropertyInfo( + containingType: typeof(global::MyApp.Customer), + propertyType: typeof(string), + name: "Name", + displayName: "Name" + ), + new GeneratedValidatablePropertyInfo( + containingType: typeof(global::MyApp.Customer), + propertyType: typeof(string), + name: "Email", + displayName: "Email" + ), + ] + ); + return true; + } + if (type == typeof(global::MyApp.Product)) + { + validatableInfo = new GeneratedValidatableTypeInfo( + type: typeof(global::MyApp.Product), + members: [ + new GeneratedValidatablePropertyInfo( + containingType: typeof(global::MyApp.Product), + propertyType: typeof(string), + name: "ProductName", + displayName: "ProductName" + ), + new GeneratedValidatablePropertyInfo( + containingType: typeof(global::MyApp.Product), + propertyType: typeof(decimal), + name: "Price", + displayName: "Price" + ), + ] + ); + return true; + } + + return false; + } + + // No-ops, rely on runtime code for ParameterInfo-based resolution + public bool TryGetValidatableParameterInfo(global::System.Reflection.ParameterInfo parameterInfo, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo) + { + validatableInfo = null; + return false; + } + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class GeneratedServiceCollectionExtensions + { + [InterceptsLocation] + public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddValidation(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::System.Action? configureOptions = null) + { + // Use non-extension method to avoid infinite recursion. + return global::Microsoft.Extensions.DependencyInjection.ValidationServiceCollectionExtensions.AddValidation(services, options => + { + options.Resolvers.Insert(0, new GeneratedValidatableInfoResolver()); + if (configureOptions is not null) + { + configureOptions(options); + } + }); + } + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class ValidationAttributeCache + { + private sealed record CacheKey( + [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + [property: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + global::System.Type ContainingType, + string PropertyName); + private static readonly global::System.Collections.Concurrent.ConcurrentDictionary _cache = new(); + + public static global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes( + [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + global::System.Type containingType, + string propertyName) + { + var key = new CacheKey(containingType, propertyName); + return _cache.GetOrAdd(key, static k => + { + var results = new global::System.Collections.Generic.List(); + + // Get attributes from the property + var property = k.ContainingType.GetProperty(k.PropertyName); + if (property != null) + { + var propertyAttributes = global::System.Reflection.CustomAttributeExtensions + .GetCustomAttributes(property, inherit: true); + + results.AddRange(propertyAttributes); + } + + // Check constructors for parameters that match the property name + // to handle record scenarios + foreach (var constructor in k.ContainingType.GetConstructors()) + { + // Look for parameter with matching name (case insensitive) + var parameter = global::System.Linq.Enumerable.FirstOrDefault( + constructor.GetParameters(), + p => string.Equals(p.Name, k.PropertyName, global::System.StringComparison.OrdinalIgnoreCase)); + + if (parameter != null) + { + var paramAttributes = global::System.Reflection.CustomAttributeExtensions + .GetCustomAttributes(parameter, inherit: true); + + results.AddRange(paramAttributes); + + break; + } + } + + return results.ToArray(); + }); + } + } +} \ No newline at end of file