Skip to content

Commit 0903772

Browse files
committed
Proof-of-concept fix for getting validation SG to work in Blazor Wasm SDK projects
1 parent fed42ad commit 0903772

File tree

5 files changed

+107
-24
lines changed

5 files changed

+107
-24
lines changed

src/Shared/RoslynUtils/SymbolExtensions.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,13 @@ public static IEnumerable<ITypeSymbol> GetThisAndBaseTypes(this ITypeSymbol? typ
4949
}
5050
}
5151

52-
public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeType)
52+
public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol? attributeType)
5353
{
54+
if (attributeType is null)
55+
{
56+
return false;
57+
}
58+
5459
foreach (var attributeData in symbol.GetAttributes())
5560
{
5661
if (SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, attributeType))
@@ -67,8 +72,13 @@ public static bool HasAttribute(this ImmutableArray<AttributeData> attributes, I
6772
return attributes.TryGetAttribute(attributeType, out _);
6873
}
6974

70-
public static bool HasAttribute(this ITypeSymbol typeSymbol, INamedTypeSymbol attributeSymbol)
75+
public static bool HasAttribute(this ITypeSymbol typeSymbol, INamedTypeSymbol? attributeSymbol)
7176
{
77+
if (attributeSymbol is null)
78+
{
79+
return false;
80+
}
81+
7282
var current = typeSymbol;
7383

7484
while (current is not null)

src/Shared/RoslynUtils/WellKnownTypes.cs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,13 @@ public INamedTypeSymbol Get(SpecialType type)
5858
return _compilation.GetSpecialType(type);
5959
}
6060

61-
public INamedTypeSymbol Get(WellKnownTypeData.WellKnownType type)
61+
public INamedTypeSymbol? Get(WellKnownTypeData.WellKnownType? type)
6262
{
63+
if (type is null)
64+
{
65+
return null;
66+
}
67+
6368
var index = (int)type;
6469
var symbol = _lazyWellKnownTypes[index];
6570
if (symbol is not null)
@@ -72,18 +77,18 @@ public INamedTypeSymbol Get(WellKnownTypeData.WellKnownType type)
7277
return GetAndCache(index);
7378
}
7479

75-
private INamedTypeSymbol GetAndCache(int index)
80+
private INamedTypeSymbol? GetAndCache(int index)
7681
{
7782
var result = GetTypeByMetadataNameInTargetAssembly(WellKnownTypeData.WellKnownTypeNames[index]);
78-
if (result == null)
79-
{
80-
throw new InvalidOperationException($"Failed to resolve well-known type '{WellKnownTypeData.WellKnownTypeNames[index]}'.");
81-
}
83+
//if (result == null)
84+
//{
85+
// throw new InvalidOperationException($"Failed to resolve well-known type '{WellKnownTypeData.WellKnownTypeNames[index]}'.");
86+
//}
8287
Interlocked.CompareExchange(ref _lazyWellKnownTypes[index], result, null);
8388

8489
// GetTypeByMetadataName should always return the same instance for a name.
8590
// To ensure we have a consistent value, for thread safety, return symbol set in the array.
86-
return _lazyWellKnownTypes[index]!;
91+
return _lazyWellKnownTypes[index];
8792
}
8893

8994
// Filter for types within well-known (framework-owned) assemblies only.
@@ -130,7 +135,7 @@ public bool IsType(ITypeSymbol type, WellKnownTypeData.WellKnownType[] wellKnown
130135
return false;
131136
}
132137

133-
public bool Implements(ITypeSymbol type, WellKnownTypeData.WellKnownType[] interfaceWellKnownTypes)
138+
public bool Implements(ITypeSymbol type, WellKnownTypeData.WellKnownType?[] interfaceWellKnownTypes)
134139
{
135140
foreach (var wellKnownType in interfaceWellKnownTypes)
136141
{
@@ -143,9 +148,9 @@ public bool Implements(ITypeSymbol type, WellKnownTypeData.WellKnownType[] inter
143148
return false;
144149
}
145150

146-
public static bool Implements(ITypeSymbol? type, ITypeSymbol interfaceType)
151+
public static bool Implements(ITypeSymbol? type, ITypeSymbol? interfaceType)
147152
{
148-
if (type is null)
153+
if (type is null || interfaceType is null)
149154
{
150155
return false;
151156
}

src/Validation/gen/Extensions/ISymbolExtensions.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@ namespace Microsoft.Extensions.Validation;
1010

1111
internal static class ISymbolExtensions
1212
{
13-
public static string GetDisplayName(this ISymbol property, INamedTypeSymbol displayAttribute)
13+
public static string GetDisplayName(this ISymbol property, INamedTypeSymbol? displayAttribute)
1414
{
15+
if (displayAttribute is null)
16+
{
17+
return property.Name;
18+
}
19+
1520
var displayNameAttribute = property.GetAttributes()
1621
.FirstOrDefault(attribute =>
1722
attribute.AttributeClass is { } attributeClass &&

src/Validation/gen/Extensions/ITypeSymbolExtensions.cs

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
45
using System.Collections.Immutable;
56
using System.Linq;
67
using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure;
@@ -21,8 +22,13 @@ public static bool IsEnumerable(this ITypeSymbol type, INamedTypeSymbol enumerab
2122
return type.ImplementsInterface(enumerable) || SymbolEqualityComparer.Default.Equals(type, enumerable);
2223
}
2324

24-
public static bool ImplementsValidationAttribute(this ITypeSymbol typeSymbol, INamedTypeSymbol validationAttributeSymbol)
25+
public static bool ImplementsValidationAttribute(this ITypeSymbol typeSymbol, INamedTypeSymbol? validationAttributeSymbol)
2526
{
27+
if (validationAttributeSymbol is null)
28+
{
29+
return false;
30+
}
31+
2632
var baseType = typeSymbol.BaseType;
2733
while (baseType != null)
2834
{
@@ -36,8 +42,9 @@ public static bool ImplementsValidationAttribute(this ITypeSymbol typeSymbol, IN
3642
return false;
3743
}
3844

39-
public static ITypeSymbol UnwrapType(this ITypeSymbol type, INamedTypeSymbol enumerable)
45+
public static ITypeSymbol UnwrapType(this ITypeSymbol type, INamedTypeSymbol? enumerable)
4046
{
47+
4148
if (type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T &&
4249
type is INamedTypeSymbol { TypeArguments.Length: 1 })
4350
{
@@ -52,6 +59,11 @@ public static ITypeSymbol UnwrapType(this ITypeSymbol type, INamedTypeSymbol enu
5259
type = type.WithNullableAnnotation(NullableAnnotation.NotAnnotated);
5360
}
5461

62+
if (enumerable is null)
63+
{
64+
return type;
65+
}
66+
5567
if (type is INamedTypeSymbol namedType && namedType.IsEnumerable(enumerable) && namedType.TypeArguments.Length == 1)
5668
{
5769
// Extract the T from an IEnumerable<T> or List<T>
@@ -61,8 +73,13 @@ public static ITypeSymbol UnwrapType(this ITypeSymbol type, INamedTypeSymbol enu
6173
return type;
6274
}
6375

64-
internal static bool ImplementsInterface(this ITypeSymbol type, ITypeSymbol interfaceType)
76+
internal static bool ImplementsInterface(this ITypeSymbol type, ITypeSymbol? interfaceType)
6577
{
78+
if (interfaceType is null)
79+
{
80+
return false;
81+
}
82+
6683
foreach (var iface in type.AllInterfaces)
6784
{
6885
if (SymbolEqualityComparer.Default.Equals(interfaceType, iface))
@@ -73,8 +90,13 @@ internal static bool ImplementsInterface(this ITypeSymbol type, ITypeSymbol inte
7390
return false;
7491
}
7592

76-
internal static ImmutableArray<INamedTypeSymbol>? GetJsonDerivedTypes(this ITypeSymbol type, INamedTypeSymbol jsonDerivedTypeAttribute)
93+
internal static ImmutableArray<INamedTypeSymbol>? GetJsonDerivedTypes(this ITypeSymbol type, INamedTypeSymbol? jsonDerivedTypeAttribute)
7794
{
95+
if (jsonDerivedTypeAttribute is null)
96+
{
97+
return null;
98+
}
99+
78100
var derivedTypes = ImmutableArray.CreateBuilder<INamedTypeSymbol>();
79101
foreach (var attribute in type.GetAttributes())
80102
{
@@ -95,7 +117,9 @@ internal static bool ImplementsInterface(this ITypeSymbol type, ITypeSymbol inte
95117
// types themselves so we short-circuit on them.
96118
internal static bool IsExemptType(this ITypeSymbol type, WellKnownTypes wellKnownTypes)
97119
{
98-
return SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_HttpContext))
120+
try
121+
{
122+
return SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_HttpContext))
99123
|| SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_HttpRequest))
100124
|| SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_HttpResponse))
101125
|| SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Threading_CancellationToken))
@@ -104,6 +128,12 @@ internal static bool IsExemptType(this ITypeSymbol type, WellKnownTypes wellKnow
104128
|| SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_IFormFile))
105129
|| SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_IO_Stream))
106130
|| SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_IO_Pipelines_PipeReader));
131+
132+
}
133+
catch (InvalidOperationException)
134+
{
135+
return false;
136+
}
107137
}
108138

109139
internal static IPropertySymbol? FindPropertyIncludingBaseTypes(this INamedTypeSymbol typeSymbol, string propertyName)
@@ -132,8 +162,13 @@ internal static bool IsExemptType(this ITypeSymbol type, WellKnownTypes wellKnow
132162
/// <param name="parameter">The parameter to check.</param>
133163
/// <param name="fromServiceMetadataSymbol">The symbol representing the [FromService] attribute.</param>
134164
/// <param name="fromKeyedServiceAttributeSymbol">The symbol representing the [FromKeyedService] attribute.</param>
135-
internal static bool IsServiceParameter(this IParameterSymbol parameter, INamedTypeSymbol fromServiceMetadataSymbol, INamedTypeSymbol fromKeyedServiceAttributeSymbol)
165+
internal static bool IsServiceParameter(this IParameterSymbol parameter, INamedTypeSymbol? fromServiceMetadataSymbol, INamedTypeSymbol? fromKeyedServiceAttributeSymbol)
136166
{
167+
if (fromServiceMetadataSymbol is null || fromKeyedServiceAttributeSymbol is null)
168+
{
169+
return false;
170+
}
171+
137172
return parameter.GetAttributes().Any(attr =>
138173
attr.AttributeClass is not null &&
139174
(attr.AttributeClass.ImplementsInterface(fromServiceMetadataSymbol) ||
@@ -146,8 +181,13 @@ attr.AttributeClass is not null &&
146181
/// <param name="property">The property to check.</param>
147182
/// <param name="fromServiceMetadataSymbol">The symbol representing the [FromServices] attribute.</param>
148183
/// <param name="fromKeyedServiceAttributeSymbol">The symbol representing the [FromKeyedServices] attribute.</param>
149-
internal static bool IsServiceProperty(this IPropertySymbol property, INamedTypeSymbol fromServiceMetadataSymbol, INamedTypeSymbol fromKeyedServiceAttributeSymbol)
184+
internal static bool IsServiceProperty(this IPropertySymbol property, INamedTypeSymbol? fromServiceMetadataSymbol, INamedTypeSymbol? fromKeyedServiceAttributeSymbol)
150185
{
186+
if (fromServiceMetadataSymbol is null || fromKeyedServiceAttributeSymbol is null)
187+
{
188+
return false;
189+
}
190+
151191
return property.GetAttributes().Any(attr =>
152192
attr.AttributeClass is not null &&
153193
(attr.AttributeClass.ImplementsInterface(fromServiceMetadataSymbol) ||
@@ -159,19 +199,29 @@ attr.AttributeClass is not null &&
159199
/// </summary>
160200
/// <param name="property">The property to check.</param>
161201
/// <param name="jsonIgnoreAttributeSymbol">The symbol representing the [JsonIgnore] attribute.</param>
162-
internal static bool IsJsonIgnoredProperty(this IPropertySymbol property, INamedTypeSymbol jsonIgnoreAttributeSymbol)
202+
internal static bool IsJsonIgnoredProperty(this IPropertySymbol property, INamedTypeSymbol? jsonIgnoreAttributeSymbol)
163203
{
204+
if (jsonIgnoreAttributeSymbol is null)
205+
{
206+
return false;
207+
}
208+
164209
return property.GetAttributes().Any(attr =>
165210
attr.AttributeClass is not null &&
166211
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, jsonIgnoreAttributeSymbol));
167212
}
168213

169-
internal static bool IsSkippedValidationProperty(this IPropertySymbol property, INamedTypeSymbol skipValidationAttributeSymbol)
214+
internal static bool IsSkippedValidationProperty(this IPropertySymbol property, INamedTypeSymbol? skipValidationAttributeSymbol)
170215
{
216+
if (skipValidationAttributeSymbol is null)
217+
{
218+
return false;
219+
}
220+
171221
return property.HasAttribute(skipValidationAttributeSymbol) || property.Type.HasAttribute(skipValidationAttributeSymbol);
172222
}
173223

174-
internal static bool IsSkippedValidationParameter(this IParameterSymbol parameter, INamedTypeSymbol skipValidationAttributeSymbol)
224+
internal static bool IsSkippedValidationParameter(this IParameterSymbol parameter, INamedTypeSymbol? skipValidationAttributeSymbol)
175225
{
176226
return parameter.HasAttribute(skipValidationAttributeSymbol) || parameter.Type.HasAttribute(skipValidationAttributeSymbol);
177227
}

src/Validation/src/Microsoft.Extensions.Validation.csproj

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<Description>Common validation abstractions and validation infrastructure for .NET applications.</Description>
@@ -11,6 +11,19 @@
1111
<EnableDefaultItems>true</EnableDefaultItems>
1212
</PropertyGroup>
1313

14+
<ItemGroup>
15+
<ProjectReference Include="$(RepoRoot)src\Validation\gen\Microsoft.Extensions.Validation.ValidationsGenerator.csproj" Pack="false">
16+
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
17+
<OutputItemType>Content</OutputItemType>
18+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
19+
</ProjectReference>
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<!-- Package the generator in the analyzer directory of the nuget package -->
24+
<None Include="$(OutputPath)\$(AssemblyName).ValidationsGenerator.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
25+
</ItemGroup>
26+
1427
<ItemGroup>
1528
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
1629
<Reference Include="Microsoft.Extensions.Options" />

0 commit comments

Comments
 (0)