Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Update for optional params
  • Loading branch information
Aleksandr Sobolev committed Oct 7, 2025
commit 5e2ea341d60a01ba2830f0fd1e906bd65f39e1e9
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
using Microsoft.CodeAnalysis;
Expand Down Expand Up @@ -253,7 +254,7 @@ private static bool IsConstraintInvalidForType(string policy, ITypeSymbol type,
return constraint switch
{
"length" or "minlength" or "maxlength" or "regex" when type.SpecialType is not SpecialType.System_String => true,
"min" or "max" or "range" when type.SpecialType < SpecialType.System_SByte || type.SpecialType > SpecialType.System_UInt64 => true,
"min" or "max" or "range" when !IsIntegerType(type) && !IsNullableIntegerType(type) => true,
_ => false
};
}
Expand All @@ -263,14 +264,14 @@ private static bool IsConstraintInvalidForType(string policy, ITypeSymbol type,

return constraint switch
{
"int" when type.SpecialType < SpecialType.System_SByte || type.SpecialType > SpecialType.System_UInt64 => true,
"bool" when type.SpecialType is not SpecialType.System_Boolean => true,
"datetime" when type.SpecialType is not SpecialType.System_DateTime => true,
"double" when type.SpecialType is not SpecialType.System_Double => true,
"guid" when !type.Equals(wellKnownTypes.Get(WellKnownType.System_Guid), SymbolEqualityComparer.Default) => true,
"long" when type.SpecialType is not SpecialType.System_Int64 and not SpecialType.System_UInt64 => true,
"decimal" when type.SpecialType is not SpecialType.System_Decimal => true,
"float" when type.SpecialType is not SpecialType.System_Single => true,
"int" when !IsIntegerType(type) && !IsNullableIntegerType(type) => true,
"bool" when !IsValueTypeOrNullableValueType(type, SpecialType.System_Boolean) => true,
"datetime" when !IsValueTypeOrNullableValueType(type, SpecialType.System_DateTime) => true,
"double" when !IsValueTypeOrNullableValueType(type, SpecialType.System_Double) => true,
"guid" when !IsGuidType(type, wellKnownTypes) && !IsNullableGuidType(type, wellKnownTypes) => true,
"long" when !IsLongType(type) && !IsNullableLongType(type) => true,
"decimal" when !IsValueTypeOrNullableValueType(type, SpecialType.System_Decimal) => true,
"float" when !IsValueTypeOrNullableValueType(type, SpecialType.System_Single) => true,
"alpha" when type.SpecialType is not SpecialType.System_String => true,
"file" or "nonfile" when type.SpecialType is not SpecialType.System_String => true,
_ => false
Expand All @@ -291,6 +292,52 @@ private static bool IsConstraintInvalidForType(string policy, ITypeSymbol type,
return null;
}

private static bool IsGuidType(ITypeSymbol type, WellKnownTypes wellKnownTypes)
{
return type.Equals(wellKnownTypes.Get(WellKnownType.System_Guid), SymbolEqualityComparer.Default);
}

private static bool IsIntegerType(ITypeSymbol type)
{
return type.SpecialType >= SpecialType.System_SByte && type.SpecialType <= SpecialType.System_UInt64;
}

private static bool IsLongType(ITypeSymbol type)
{
return type.SpecialType is SpecialType.System_Int64 or SpecialType.System_UInt64;
}

private static bool IsNullableGuidType(ITypeSymbol type, WellKnownTypes wellKnownTypes)
{
return IsNullableType(type, out var namedType) && IsGuidType(namedType.TypeArguments[0], wellKnownTypes);
}

private static bool IsNullableIntegerType(ITypeSymbol type)
{
return IsNullableType(type, out var namedType) && IsIntegerType(namedType.TypeArguments[0]);
}

private static bool IsNullableLongType(ITypeSymbol type)
{
return IsNullableType(type, out var namedType) && IsLongType(namedType.TypeArguments[0]);
}

public static bool IsNullableType(ITypeSymbol type, [NotNullWhen(true)] out INamedTypeSymbol? namedType)
{
namedType = type as INamedTypeSymbol;
return namedType != null && namedType.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T;
}

private static bool IsNullableValueType(ITypeSymbol type, SpecialType specialType)
{
return IsNullableType(type, out var namedType) && namedType.TypeArguments[0].SpecialType == specialType;
}

private static bool IsValueTypeOrNullableValueType(ITypeSymbol type, SpecialType specialType)
{
return type.SpecialType == specialType || IsNullableValueType(type, specialType);
}

private record struct MapOperation(IOperation? Builder, IInvocationOperation Operation, RouteUsageModel RouteUsageModel)
{
public static MapOperation Create(IInvocationOperation operation, RouteUsageModel routeUsageModel)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,15 +300,15 @@ public async Task OptionalRouteParamRequiredArgument_WithRegexConstraint_Produce
using Microsoft.AspNetCore.Builder;

var app = WebApplication.Create();
app.MapGet(""/hello/{age:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)?}"", ({|#0:int age|}) => $""Age: {age}"");
app.MapGet(""/hello/{age:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)?}"", ({|#0:string age|}) => $""Age: {age}"");
";

var fixedSource = @"
#nullable enable
using Microsoft.AspNetCore.Builder;

var app = WebApplication.Create();
app.MapGet(""/hello/{age:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)?}"", (int? age) => $""Age: {age}"");
app.MapGet(""/hello/{age:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)?}"", (string? age) => $""Age: {age}"");
";
var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.DetectMismatchedParameterOptionality).WithArguments("age").WithLocation(0);

Expand Down
Loading