Skip to content

Commit a1fbd8c

Browse files
committed
tmp
1 parent f1f768f commit a1fbd8c

File tree

3 files changed

+108
-187
lines changed

3 files changed

+108
-187
lines changed

src/Validation/gen/Parsers/ValidationsGenerator.AttributeParser.cs

Lines changed: 0 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -29,105 +29,4 @@ internal ImmutableArray<ValidatableType> TransformValidatableTypeWithAttribute(G
2929
}
3030
return [];
3131
}
32-
33-
internal static bool ShouldTransformSymbolWithEmbeddedValidatableTypeAttribute(SyntaxNode syntaxNode, CancellationToken _)
34-
{
35-
// Only process class and record declarations
36-
if (syntaxNode is not (ClassDeclarationSyntax or RecordDeclarationSyntax))
37-
{
38-
return false;
39-
}
40-
41-
// Check if the type has any attribute that could be ValidatableTypeAttribute
42-
var typeDeclaration = (TypeDeclarationSyntax)syntaxNode;
43-
foreach (var attributeList in typeDeclaration.AttributeLists)
44-
{
45-
foreach (var attribute in attributeList.Attributes)
46-
{
47-
if (IsValidatableTypeAttribute(attribute))
48-
{
49-
return true;
50-
}
51-
}
52-
}
53-
54-
return false;
55-
}
56-
57-
internal ImmutableArray<ValidatableType> TransformValidatableTypeWithEmbeddedValidatableTypeAttribute(
58-
GeneratorSyntaxContext context,
59-
GeneratorSettings settings,
60-
CancellationToken cancellationToken)
61-
{
62-
if (context.Node is not (ClassDeclarationSyntax or RecordDeclarationSyntax))
63-
{
64-
return [];
65-
}
66-
67-
var typeSymbol = context.SemanticModel.GetDeclaredSymbol(context.Node, cancellationToken) as ITypeSymbol;
68-
if (typeSymbol == null)
69-
{
70-
return [];
71-
}
72-
73-
var validatableTypeAttributeName = $"{settings.RootNamespace}.ValidatableTypeAttribute";
74-
// Check if the type has a ValidatableTypeAttribute (auto-generated)
75-
if (!HasValidatableTypeAttribute(typeSymbol, validatableTypeAttributeName))
76-
{
77-
return [];
78-
}
79-
80-
var validatableTypes = new HashSet<ValidatableType>(ValidatableTypeComparer.Instance);
81-
List<ITypeSymbol> visitedTypes = [];
82-
var wellKnownTypes = WellKnownTypes.GetOrCreate(context.SemanticModel.Compilation);
83-
84-
if (TryExtractValidatableType(typeSymbol, wellKnownTypes, ref validatableTypes, ref visitedTypes))
85-
{
86-
return [.. validatableTypes];
87-
}
88-
return [];
89-
}
90-
91-
private static bool IsValidatableTypeAttribute(AttributeSyntax attribute)
92-
{
93-
var name = attribute.Name.ToString();
94-
return name is "ValidatableType" or "ValidatableTypeAttribute" ||
95-
name.EndsWith(".ValidatableType", StringComparison.Ordinal) ||
96-
name.EndsWith(".ValidatableTypeAttribute", StringComparison.Ordinal);
97-
}
98-
99-
private static bool HasValidatableTypeAttribute(ITypeSymbol typeSymbol, string validatableTypeAttributeName)
100-
{
101-
foreach (var attr in typeSymbol.GetAttributes())
102-
{
103-
var attributeClass = attr.AttributeClass;
104-
if (attributeClass is null)
105-
{
106-
continue;
107-
}
108-
109-
var name = attributeClass.Name;
110-
var namespaceName = attributeClass.ContainingNamespace.ToDisplayString();
111-
var fullName = $"{namespaceName}.{name}";
112-
if (!string.Equals(fullName, validatableTypeAttributeName, StringComparison.Ordinal))
113-
{
114-
continue;
115-
}
116-
117-
// Check for auto-generated attribute (any namespace)
118-
if (name == validatableTypeAttributeName)
119-
{
120-
// Additional check: ensure it's marked with [Embedded] to confirm it's auto-generated
121-
foreach (var embeddedAttr in attributeClass.GetAttributes())
122-
{
123-
if (embeddedAttr.AttributeClass?.ToDisplayString() == "Microsoft.CodeAnalysis.EmbeddedAttribute")
124-
{
125-
return true;
126-
}
127-
}
128-
}
129-
}
130-
131-
return false;
132-
}
13332
}

src/Validation/gen/ValidationsGenerator.cs

Lines changed: 14 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -14,56 +14,31 @@ public sealed partial class ValidationsGenerator : IIncrementalGenerator
1414
{
1515
public void Initialize(IncrementalGeneratorInitializationContext context)
1616
{
17-
// Get the MSBuild properties for controlling attribute generation
18-
var generatorSettings = context.AnalyzerConfigOptionsProvider
19-
.Select((configOptions, _) =>
20-
{
21-
configOptions.GlobalOptions.TryGetValue("build_property.RootNamespace", out var rootNamespace);
22-
configOptions.GlobalOptions.TryGetValue("build_property.GenerateBuiltinValidatableTypeAttribute", out var generateAttribute);
23-
24-
var shouldGenerate = !string.IsNullOrEmpty(rootNamespace) &&
25-
string.Equals(generateAttribute, "true", StringComparison.OrdinalIgnoreCase);
26-
27-
return new GeneratorSettings(rootNamespace, shouldGenerate);
28-
});
29-
30-
// Emit the ValidatableTypeAttribute conditionally
31-
context.RegisterSourceOutput(generatorSettings, EmitValidatableTypeAttribute);
32-
3317
// Find the builder.Services.AddValidation() call in the application.
3418
var addValidation = context.SyntaxProvider.CreateSyntaxProvider(
3519
predicate: FindAddValidation,
3620
transform: TransformAddValidation
3721
);
3822

39-
// Extract types that have been marked with [ValidatableType].
40-
// This handles both the framework attribute and the auto-generated attribute.
41-
var validatableTypesWithAttribute = context.SyntaxProvider.ForAttributeWithMetadataName(
23+
// Extract types that have been marked with framework [ValidatableType].
24+
var frameworkValidatableTypes = context.SyntaxProvider.ForAttributeWithMetadataName(
4225
"Microsoft.Extensions.Validation.ValidatableTypeAttribute",
4326
predicate: ShouldTransformSymbolWithAttribute,
4427
transform: TransformValidatableTypeWithAttribute
4528
);
4629

47-
// Extract types that have been marked with the embedded [ValidatableType].
48-
var generatedValidatableTypesWithAttribute = context.SyntaxProvider.CreateSyntaxProvider(
49-
predicate: ShouldTransformSymbolWithEmbeddedValidatableTypeAttribute,
50-
transform: (context, cancellation) => context
51-
).Combine(generatorSettings)
52-
.Select((combined, cancellation) =>
53-
{
54-
var (syntaxContext, settings) = combined;
55-
if (!settings.ShouldGenerateAttribute || string.IsNullOrEmpty(settings.RootNamespace))
56-
{
57-
return [];
58-
}
59-
60-
return TransformValidatableTypeWithEmbeddedValidatableTypeAttribute(syntaxContext, settings, cancellation);
61-
})
62-
.Where(t => t.Length > 0);
30+
// Extract types that have been marked with generated [ValidatableType].
31+
var generatedValidatableTypes = context.SyntaxProvider.ForAttributeWithMetadataName(
32+
"Microsoft.Extensions.Validation.Generated.ValidatableTypeAttribute",
33+
predicate: ShouldTransformSymbolWithAttribute,
34+
transform: TransformValidatableTypeWithAttribute
35+
);
6336

64-
var allValidatableTypes = generatedValidatableTypesWithAttribute.Collect()
65-
.Combine(validatableTypesWithAttribute.Collect())
66-
.SelectMany((pair, cancellation) => pair.Left.Concat(pair.Right).ToImmutableArray());
37+
// Combine both sources of validatable types
38+
var validatableTypesWithAttribute = frameworkValidatableTypes
39+
.Collect()
40+
.Combine(generatedValidatableTypes.Collect())
41+
.SelectMany((pair, _) => pair.Left.Concat(pair.Right).ToImmutableArray());
6742

6843
// Extract all minimal API endpoints in the application.
6944
var endpoints = context.SyntaxProvider
@@ -77,7 +52,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
7752
.Select(ExtractValidatableEndpoint);
7853

7954
// Join all validatable types encountered in the type graph.
80-
var validatableTypes = allValidatableTypes
55+
var validatableTypes = validatableTypesWithAttribute
8156
.Concat(validatableTypesFromEndpoints)
8257
.Distinct(ValidatableTypeComparer.Instance)
8358
.Collect();
@@ -89,43 +64,4 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
8964
// ValidatableTypeInfo for all validatable types.
9065
context.RegisterSourceOutput(emitInputs, Emit);
9166
}
92-
93-
private static void EmitValidatableTypeAttribute(SourceProductionContext context, GeneratorSettings settings)
94-
{
95-
// Only emit if the build property is set to true and root namespace is provided
96-
if (!settings.ShouldGenerateAttribute)
97-
{
98-
return;
99-
}
100-
101-
// First, emit the EmbeddedAttribute if it doesn't exist
102-
var embeddedAttributeSource = """
103-
// <auto-generated/>
104-
namespace Microsoft.CodeAnalysis
105-
{
106-
[global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)]
107-
internal sealed class EmbeddedAttribute : global::System.Attribute
108-
{
109-
}
110-
}
111-
""";
112-
113-
context.AddSource("EmbeddedAttribute.g.cs", embeddedAttributeSource);
114-
115-
// Generate the ValidatableTypeAttribute in the project's root namespace
116-
var validatableTypeAttributeSource = $$"""
117-
// <auto-generated/>
118-
namespace {{settings.RootNamespace}};
119-
120-
[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
121-
[global::System.AttributeUsage(global::System.AttributeTargets.Class)]
122-
internal sealed class ValidatableTypeAttribute : global::System.Attribute
123-
{
124-
}
125-
""";
126-
127-
context.AddSource("ValidatableTypeAttribute.g.cs", validatableTypeAttributeSource);
128-
}
129-
130-
internal readonly record struct GeneratorSettings(string? RootNamespace, bool ShouldGenerateAttribute);
13167
}

src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.AutoGeneratedAttribute.cs

Lines changed: 94 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,18 @@ namespace Microsoft.Extensions.Validation.GeneratorTests;
1111
public partial class ValidationsGeneratorTests : ValidationsGeneratorTestBase
1212
{
1313
[Fact]
14-
public async Task CanUseAutoGeneratedValidatableTypeAttribute()
14+
public async Task CanDiscoverGeneratedValidatableTypeAttribute()
1515
{
1616
var source = """
1717
using Microsoft.AspNetCore.Builder;
1818
using Microsoft.Extensions.DependencyInjection;
1919
using System.ComponentModel.DataAnnotations;
20+
using Microsoft.Extensions.Validation.Generated;
2021
2122
var builder = WebApplication.CreateBuilder();
2223
builder.Services.AddValidation();
2324
var app = builder.Build();
2425
25-
// Using the auto-generated ValidatableTypeAttribute
26-
// No manual attribute definition needed!
2726
[ValidatableType]
2827
public class Customer
2928
{
@@ -39,10 +38,97 @@ public class Customer
3938
app.Run();
4039
""";
4140

42-
await Verify(source, out var compilation, globalOptions: new Dictionary<string, string>
43-
{
44-
["build_property.RootNamespace"] = "TestApp",
45-
["build_property.GenerateBuiltinValidatableTypeAttribute"] = "true"
46-
});
41+
// Simulate the Razor SDK generating the attribute
42+
var generatedAttributeSource = """
43+
// <auto-generated/>
44+
namespace Microsoft.CodeAnalysis
45+
{
46+
[global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)]
47+
internal sealed class EmbeddedAttribute : global::System.Attribute
48+
{
49+
}
50+
}
51+
52+
namespace Microsoft.Extensions.Validation.Generated;
53+
54+
[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
55+
[global::System.AttributeUsage(global::System.AttributeTargets.Class)]
56+
internal sealed class ValidatableTypeAttribute : global::System.Attribute
57+
{
58+
}
59+
""";
60+
61+
// Combine the generated attribute with the user's source
62+
var combinedSource = generatedAttributeSource + "\n" + source;
63+
64+
await Verify(combinedSource, out var compilation);
65+
}
66+
67+
[Fact]
68+
public async Task CanUseBothFrameworkAndGeneratedValidatableTypeAttributes()
69+
{
70+
var source = """
71+
using Microsoft.AspNetCore.Builder;
72+
using Microsoft.Extensions.DependencyInjection;
73+
using System.ComponentModel.DataAnnotations;
74+
using Microsoft.Extensions.Validation;
75+
using Microsoft.Extensions.Validation.Generated;
76+
77+
var builder = WebApplication.CreateBuilder();
78+
builder.Services.AddValidation();
79+
var app = builder.Build();
80+
81+
// Using framework attribute
82+
[Microsoft.Extensions.Validation.ValidatableType]
83+
public class Customer
84+
{
85+
[Required]
86+
public string Name { get; set; } = "";
87+
88+
[EmailAddress]
89+
public string Email { get; set; } = "";
90+
}
91+
92+
// Using generated attribute
93+
[ValidatableType]
94+
public class Product
95+
{
96+
[Required]
97+
public string ProductName { get; set; } = "";
98+
99+
[Range(0, double.MaxValue)]
100+
public decimal Price { get; set; }
101+
}
102+
103+
app.MapPost("/customers", (Customer customer) => "OK");
104+
app.MapPost("/products", (Product product) => "OK");
105+
106+
app.Run();
107+
""";
108+
109+
// Simulate the Razor SDK generating the attribute
110+
var generatedAttributeSource = """
111+
// <auto-generated/>
112+
namespace Microsoft.CodeAnalysis
113+
{
114+
[global::System.AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)]
115+
internal sealed class EmbeddedAttribute : global::System.Attribute
116+
{
117+
}
118+
}
119+
120+
namespace Microsoft.Extensions.Validation.Generated;
121+
122+
[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
123+
[global::System.AttributeUsage(global::System.AttributeTargets.Class)]
124+
internal sealed class ValidatableTypeAttribute : global::System.Attribute
125+
{
126+
}
127+
""";
128+
129+
// Combine the generated attribute with the user's source
130+
var combinedSource = generatedAttributeSource + "\n" + source;
131+
132+
await Verify(combinedSource, out var compilation);
47133
}
48134
}

0 commit comments

Comments
 (0)