Skip to content

Commit 6b9050e

Browse files
committed
Code review fixes, additional test
1 parent e551d41 commit 6b9050e

File tree

5 files changed

+100
-33
lines changed

5 files changed

+100
-33
lines changed

src/Shared/RoslynUtils/SymbolExtensions.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,25 @@ public static bool HasAttribute(this ImmutableArray<AttributeData> attributes, I
6767
return attributes.TryGetAttribute(attributeType, out _);
6868
}
6969

70+
public static bool HasAttribute(this ITypeSymbol typeSymbol, INamedTypeSymbol attributeSymbol)
71+
{
72+
var current = typeSymbol;
73+
74+
while (current is not null)
75+
{
76+
if (current.GetAttributes().Any(attr =>
77+
attr.AttributeClass is not null &&
78+
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, attributeSymbol)))
79+
{
80+
return true;
81+
}
82+
83+
current = current.BaseType;
84+
}
85+
86+
return false;
87+
}
88+
7089
public static bool TryGetAttribute(this ImmutableArray<AttributeData> attributes, INamedTypeSymbol attributeType, [NotNullWhen(true)] out AttributeData? matchedAttribute)
7190
{
7291
foreach (var attributeData in attributes)

src/Validation/gen/Extensions/ITypeSymbolExtensions.cs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -175,21 +175,4 @@ internal static bool IsSkippedValidationParameter(this IParameterSymbol paramete
175175
{
176176
return parameter.HasAttribute(skipValidationAttributeSymbol) || parameter.Type.HasAttribute(skipValidationAttributeSymbol);
177177
}
178-
179-
internal static bool HasAttribute(this ITypeSymbol? typeSymbol, INamedTypeSymbol attributeSymbol)
180-
{
181-
while (typeSymbol is not null)
182-
{
183-
if (typeSymbol.GetAttributes().Any(attr =>
184-
attr.AttributeClass is not null &&
185-
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, attributeSymbol)))
186-
{
187-
return true;
188-
}
189-
190-
typeSymbol = typeSymbol.BaseType;
191-
}
192-
193-
return false;
194-
}
195178
}

src/Validation/src/SkipValidationAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Microsoft.Extensions.Validation;
1010
/// When applied to a property, validation is skipped for that property.
1111
/// When applied to a parameter, validation is skipped for that parameter.
1212
/// When applied to a type, validation is skipped for all properties and parameters of that type.
13-
/// This includes skipping validation of nested propeeties for complex types.
13+
/// This includes skipping validation of nested properties for complex types.
1414
/// </summary>
1515
[Experimental("ASP0029", UrlFormat = "https://aka.ms/aspnet/analyzer/{0}")]
1616
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]

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

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ public class ComplexType
4848
4949
public NestedType ObjectProperty { get; set; } = new NestedType();
5050
51+
[SkipValidation]
52+
public List<NestedType> SkippedListOfNestedTypes { get; set; } = [];
53+
54+
public List<NestedType> ListOfNestedTypes { get; set; } = [];
55+
5156
[SkipValidation]
5257
public NonSkippedBaseType SkippedBaseTypeProperty { get; set; } = new NonSkippedBaseType();
5358
@@ -100,13 +105,32 @@ await VerifyValidatableType(compilation, "ComplexType", async (validationOptions
100105
{
101106
Assert.True(validationOptions.TryGetValidatableTypeInfo(type, out var validatableTypeInfo));
102107

103-
await InvalidNestedIntegerWithRangeProducesError(validatableTypeInfo);
104-
await InvalidSkippedNestedIntegerWithRangeDoesNotProduceProduceError(validatableTypeInfo);
105-
await InvalidSkippedIntegerWithRangeDoesNotProduceError(validatableTypeInfo);
106-
await InvalidSubTypeNestedIntegersWithRangeProduceErrors(validatableTypeInfo);
107-
await InvalidAlwaysSkippedTypeDoesNotProduceError(validatableTypeInfo);
108+
await InvalidSkippedInteger_DoesNotProduceError(validatableTypeInfo);
109+
await InvalidNestedInteger_ProducesError(validatableTypeInfo);
110+
await InvalidSkippedNestedInteger_DoesNotProduceError(validatableTypeInfo);
111+
await InvalidList_ProducesError(validatableTypeInfo);
112+
await InvalidSkippedList_DoesNotProduceError(validatableTypeInfo);
113+
await InvalidSubTypeNestedIntegers_ProduceErrors(validatableTypeInfo);
114+
await InvalidAlwaysSkippedType_DoesNotProduceError(validatableTypeInfo);
108115

109-
async Task InvalidNestedIntegerWithRangeProducesError(IValidatableInfo validatableInfo)
116+
async Task InvalidSkippedInteger_DoesNotProduceError(IValidatableInfo validatableInfo)
117+
{
118+
var instance = Activator.CreateInstance(type);
119+
var intProperty = type.GetProperty("IntegerWithRange");
120+
intProperty?.SetValue(instance, 5); // Set invalid value
121+
122+
var context = new ValidateContext
123+
{
124+
ValidationOptions = validationOptions,
125+
ValidationContext = new ValidationContext(instance)
126+
};
127+
128+
await validatableTypeInfo.ValidateAsync(instance, context, CancellationToken.None);
129+
130+
Assert.Null(context.ValidationErrors);
131+
}
132+
133+
async Task InvalidNestedInteger_ProducesError(IValidatableInfo validatableInfo)
110134
{
111135
var instance = Activator.CreateInstance(type);
112136
var objectPropertyInstance = type.GetProperty("ObjectProperty").GetValue(instance);
@@ -128,7 +152,7 @@ async Task InvalidNestedIntegerWithRangeProducesError(IValidatableInfo validatab
128152
});
129153
}
130154

131-
async Task InvalidSkippedNestedIntegerWithRangeDoesNotProduceProduceError(IValidatableInfo validatableInfo)
155+
async Task InvalidSkippedNestedInteger_DoesNotProduceError(IValidatableInfo validatableInfo)
132156
{
133157
var instance = Activator.CreateInstance(type);
134158
var objectPropertyInstance = type.GetProperty("SkippedObjectProperty").GetValue(instance);
@@ -146,24 +170,59 @@ async Task InvalidSkippedNestedIntegerWithRangeDoesNotProduceProduceError(IValid
146170
Assert.Null(context.ValidationErrors);
147171
}
148172

149-
async Task InvalidSkippedIntegerWithRangeDoesNotProduceError(IValidatableInfo validatableInfo)
173+
async Task InvalidList_ProducesError(IValidatableInfo validatableInfo)
150174
{
151-
var instance = Activator.CreateInstance(type);
152-
var intProperty = type.GetProperty("IntegerWithRange");
153-
intProperty?.SetValue(instance, 5); // Set invalid value
175+
var rootInstance = Activator.CreateInstance(type);
176+
var listInstance = Activator.CreateInstance(typeof(List<>).MakeGenericType(type.Assembly.GetType("NestedType")!));
177+
178+
// Create invalid item
179+
var nestedTypeInstance = Activator.CreateInstance(type.Assembly.GetType("NestedType")!);
180+
nestedTypeInstance.GetType().GetProperty("IntegerWithRange")?.SetValue(nestedTypeInstance, 5);
181+
182+
// Add to list
183+
listInstance.GetType().GetMethod("Add")?.Invoke(listInstance, [nestedTypeInstance]);
154184

185+
type.GetProperty("ListOfNestedTypes")?.SetValue(rootInstance, listInstance);
155186
var context = new ValidateContext
156187
{
157188
ValidationOptions = validationOptions,
158-
ValidationContext = new ValidationContext(instance)
189+
ValidationContext = new ValidationContext(rootInstance)
159190
};
160191

161-
await validatableTypeInfo.ValidateAsync(instance, context, CancellationToken.None);
192+
await validatableTypeInfo.ValidateAsync(rootInstance, context, CancellationToken.None);
193+
194+
Assert.Collection(context.ValidationErrors, kvp =>
195+
{
196+
Assert.Equal("ListOfNestedTypes[0].IntegerWithRange", kvp.Key);
197+
Assert.Equal("The field IntegerWithRange must be between 10 and 100.", kvp.Value.Single());
198+
});
199+
}
200+
201+
async Task InvalidSkippedList_DoesNotProduceError(IValidatableInfo validatableInfo)
202+
{
203+
var rootInstance = Activator.CreateInstance(type);
204+
var listInstance = Activator.CreateInstance(typeof(List<>).MakeGenericType(type.Assembly.GetType("NestedType")!));
205+
206+
// Create invalid item
207+
var nestedTypeInstance = Activator.CreateInstance(type.Assembly.GetType("NestedType")!);
208+
nestedTypeInstance.GetType().GetProperty("IntegerWithRange")?.SetValue(nestedTypeInstance, 5);
209+
210+
// Add to list
211+
listInstance.GetType().GetMethod("Add")?.Invoke(listInstance, [nestedTypeInstance]);
212+
213+
type.GetProperty("SkippedListOfNestedTypes")?.SetValue(rootInstance, listInstance);
214+
var context = new ValidateContext
215+
{
216+
ValidationOptions = validationOptions,
217+
ValidationContext = new ValidationContext(rootInstance)
218+
};
219+
220+
await validatableTypeInfo.ValidateAsync(rootInstance, context, CancellationToken.None);
162221

163222
Assert.Null(context.ValidationErrors);
164223
}
165224

166-
async Task InvalidSubTypeNestedIntegersWithRangeProduceErrors(IValidatableInfo validatableInfo)
225+
async Task InvalidSubTypeNestedIntegers_ProduceErrors(IValidatableInfo validatableInfo)
167226
{
168227
var instance = Activator.CreateInstance(type);
169228
var objectPropertyInstance = type.GetProperty("NonSkippedSubTypeProperty").GetValue(instance);
@@ -194,7 +253,7 @@ async Task InvalidSubTypeNestedIntegersWithRangeProduceErrors(IValidatableInfo v
194253
});
195254
}
196255

197-
async Task InvalidAlwaysSkippedTypeDoesNotProduceError(IValidatableInfo validatableInfo)
256+
async Task InvalidAlwaysSkippedType_DoesNotProduceError(IValidatableInfo validatableInfo)
198257
{
199258
var instance = Activator.CreateInstance(type);
200259
var objectPropertyInstance = type.GetProperty("AlwaysSkippedProperty").GetValue(instance);

src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.DoesNotEmit_ForSkipValidationAttribute_OnClassProperties#ValidatableInfoResolver.g.verified.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ public bool TryGetValidatableTypeInfo(global::System.Type type, [global::System.
118118
name: "ObjectProperty",
119119
displayName: "ObjectProperty"
120120
),
121+
new GeneratedValidatablePropertyInfo(
122+
containingType: typeof(global::ComplexType),
123+
propertyType: typeof(global::System.Collections.Generic.List<global::NestedType>),
124+
name: "ListOfNestedTypes",
125+
displayName: "ListOfNestedTypes"
126+
),
121127
new GeneratedValidatablePropertyInfo(
122128
containingType: typeof(global::ComplexType),
123129
propertyType: typeof(global::NonSkippedSubType),

0 commit comments

Comments
 (0)