Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions src/Validation/gen/Emitters/ValidationsGenerator.Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ namespace Microsoft.Extensions.Validation.Generated
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)]
[param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors)]
global::System.Type containingType,
global::System.Type propertyType,
string name,
Expand All @@ -67,7 +67,7 @@ public GeneratedValidatablePropertyInfo(
Name = name;
}

[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors)]
internal global::System.Type ContainingType { get; }
internal string Name { get; }

Expand Down Expand Up @@ -132,17 +132,19 @@ file static class GeneratedServiceCollectionExtensions
{{GeneratedCodeAttribute}}
file static class ValidationAttributeCache
{
private static readonly global::System.Reflection.BindingFlags PropertyBindingFlags = global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance;

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)]
[param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors)]
[property: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors)]
global::System.Type ContainingType,
string PropertyName);
private static readonly global::System.Collections.Concurrent.ConcurrentDictionary<CacheKey, global::System.ComponentModel.DataAnnotations.ValidationAttribute[]> _propertyCache = new();
private static readonly global::System.Lazy<global::System.Collections.Concurrent.ConcurrentDictionary<global::System.Type, global::System.ComponentModel.DataAnnotations.ValidationAttribute[]>> _lazyTypeCache = new (() => new ());
private static global::System.Collections.Concurrent.ConcurrentDictionary<global::System.Type, global::System.ComponentModel.DataAnnotations.ValidationAttribute[]> TypeCache => _lazyTypeCache.Value;

public static global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetPropertyValidationAttributes(
[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
[global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors)]
global::System.Type containingType,
string propertyName)
{
Expand All @@ -152,7 +154,7 @@ private sealed record CacheKey(
var results = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>();

// Get attributes from the property
var property = k.ContainingType.GetProperty(k.PropertyName);
var property = k.ContainingType.GetProperty(k.PropertyName, PropertyBindingFlags);
if (property != null)
{
var propertyAttributes = global::System.Reflection.CustomAttributeExtensions
Expand All @@ -163,7 +165,7 @@ private sealed record CacheKey(

// Check constructors for parameters that match the property name
// to handle record scenarios
foreach (var constructor in k.ContainingType.GetConstructors())
foreach (var constructor in k.ContainingType.GetConstructors(PropertyBindingFlags))
{
// Look for parameter with matching name (case insensitive)
var parameter = global::System.Linq.Enumerable.FirstOrDefault(
Expand Down
32 changes: 32 additions & 0 deletions src/Validation/gen/Models/GeneratorConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;

namespace Microsoft.Extensions.Validation;

/// <summary>
/// Represents the configuration options for the validation source generator.
/// </summary>
internal readonly record struct GeneratorConfiguration(AccessibilityFilter AccessibilityFilter)
{
public static readonly GeneratorConfiguration Default = new(AccessibilityFilter.PublicOnly);
public static GeneratorConfiguration IncludeInternalTypes() => new(AccessibilityFilter.InternalOrPublic);
}

// Filter to check if validatable type should be extracted based on it's accessibility
internal sealed record AccessibilityFilter(ImmutableHashSet<Accessibility> Hash)
{
internal AccessibilityFilter(params Accessibility[] values) : this(ImmutableHashSet.Create(values)) {}

internal bool Match(Accessibility value) => Hash.Contains(value);
internal bool Match(params Accessibility[] values) => values.All(Hash.Contains);

internal AccessibilityFilter Extend(params Accessibility[] values) => new(Hash.Union(values));
internal AccessibilityFilter Except(params Accessibility[] values) => new(Hash.Except(values));

internal static AccessibilityFilter PublicOnly { get; } = new(ImmutableHashSet.Create(Accessibility.Public));
internal static AccessibilityFilter InternalOrPublic { get; } = new(ImmutableHashSet.Create(Accessibility.Public, Accessibility.Internal));
}
8 changes: 8 additions & 0 deletions src/Validation/gen/Models/TypeSymbolExtraction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
using Microsoft.CodeAnalysis;

namespace Microsoft.Extensions.Validation;

internal readonly record struct TypeSymbolExtraction(ITypeSymbol symbol, WellKnownTypes wellKnownTypes);
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@ internal static bool ShouldTransformSymbolWithAttribute(SyntaxNode syntaxNode, C
return syntaxNode is ClassDeclarationSyntax or RecordDeclarationSyntax;
}

internal ImmutableArray<ValidatableType> TransformValidatableTypeWithAttribute(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken)
internal TypeSymbolExtraction ExtractValidatableTypeWithAttributeSymbol(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken)
{
var wellKnownTypes = WellKnownTypes.GetOrCreate(context.SemanticModel.Compilation);
return new((ITypeSymbol)context.TargetSymbol, wellKnownTypes);
}

internal ImmutableArray<ValidatableType> RetriveValidatableTypes((TypeSymbolExtraction extraction, GeneratorConfiguration configuration) input, CancellationToken cancellationToken)
{
var validatableTypes = new HashSet<ValidatableType>(ValidatableTypeComparer.Instance);
List<ITypeSymbol> visitedTypes = [];
var wellKnownTypes = WellKnownTypes.GetOrCreate(context.SemanticModel.Compilation);
if (TryExtractValidatableType((ITypeSymbol)context.TargetSymbol, wellKnownTypes, ref validatableTypes, ref visitedTypes))
if (TryExtractValidatableType(input.extraction.symbol, input.extraction.wellKnownTypes, ref validatableTypes, ref visitedTypes, input.configuration))
{
return [.. validatableTypes];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@ internal bool FindEndpoints(SyntaxNode syntaxNode, CancellationToken cancellatio
}

internal ImmutableArray<ValidatableType> ExtractValidatableEndpoint(IInvocationOperation? operation, CancellationToken cancellationToken)
=> ExtractValidatableEndpoint((operation, configuration: GeneratorConfiguration.Default), cancellationToken);
internal ImmutableArray<ValidatableType> ExtractValidatableEndpoint((IInvocationOperation? operation, GeneratorConfiguration configuration) input, CancellationToken cancellationToken)
{
AnalyzerDebug.Assert(operation != null, "Operation should not be null.");
AnalyzerDebug.Assert(operation.SemanticModel != null, "Operation should have a semantic model.");
var wellKnownTypes = WellKnownTypes.GetOrCreate(operation.SemanticModel.Compilation);
var validatableTypes = ExtractValidatableTypes(operation, wellKnownTypes);
AnalyzerDebug.Assert(input.operation != null, "Operation should not be null.");
AnalyzerDebug.Assert(input.operation.SemanticModel != null, "Operation should have a semantic model.");
var wellKnownTypes = WellKnownTypes.GetOrCreate(input.operation.SemanticModel.Compilation);
var validatableTypes = ExtractValidatableTypes(input.operation, wellKnownTypes, input.configuration);
return validatableTypes;
}
}
Loading
Loading