From 2b6318aa0c05b62569dc0e091eefaf5d402864e5 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Thu, 15 Dec 2022 14:02:14 +1100 Subject: [PATCH 1/8] Add support for handling FromRoute attribute with route parameter renaming. --- ...llowNonParsableComplexTypesOnParameters.cs | 38 ++++++++++-- ...NonParsableComplexTypesOnParametersTest.cs | 60 +++++++++++++++++++ 2 files changed, 93 insertions(+), 5 deletions(-) diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs index 036c4bbf37f8..06a346757ead 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs @@ -64,8 +64,10 @@ private static void DisallowNonParsableComplexTypesOnParameters( location )) { continue; } + var routeParameterName = ResolveRouteParameterName(handlerDelegateParameter, wellKnownTypes); + // Match handler parameter against route parameters. If it is a route parameter it needs to be parsable/bindable in some fashion. - if (routeUsage.RoutePattern.TryGetRouteParameter(handlerDelegateParameter.Name, out var routeParameter)) + if (routeUsage.RoutePattern.TryGetRouteParameter(routeParameterName, out var routeParameter)) { var parsability = ParsabilityHelper.GetParsability(parameterTypeSymbol, wellKnownTypes); var bindability = ParsabilityHelper.GetBindability(parameterTypeSymbol, wellKnownTypes); @@ -77,7 +79,7 @@ private static void DisallowNonParsableComplexTypesOnParameters( context.ReportDiagnostic(Diagnostic.Create( descriptor, location, - routeParameter.Name, + handlerDelegateParameter.Name, parameterTypeSymbol.Name )); } @@ -86,10 +88,36 @@ private static void DisallowNonParsableComplexTypesOnParameters( } } - static bool HasAttributeImplementingFromMetadataInterfaceType(IParameterSymbol parameter, WellKnownType fromMetadataInterfaceType, WellKnownTypes wellKnownTypes) + static string ResolveRouteParameterName(IParameterSymbol parameterSymbol, WellKnownTypes wellKnownTypes) + { + if (!TryGetFromMetadataInterfaceTypeAttributeData(parameterSymbol, WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromRouteMetadata, wellKnownTypes, out var attributeData)) + { + return parameterSymbol.Name; // No route metadata attribute! + } + + foreach (var namedArgument in attributeData.NamedArguments) + { + if (namedArgument.Key == "Name") + { + var routeParameterNameConstant = namedArgument.Value; + var routeParameterName = (string)routeParameterNameConstant.Value!; + return routeParameterName; // Have attribute & name is specified. + } + } + + return parameterSymbol.Name; // We have the attribute, but name isn't specified! + } + + static bool TryGetFromMetadataInterfaceTypeAttributeData(IParameterSymbol parameterSymbol, WellKnownType fromMetadataInterfaceType, WellKnownTypes wellknowTypes, out AttributeData attributeData) + { + var fromMetadataInterfaceTypeSymbol = wellknowTypes.Get(fromMetadataInterfaceType); + attributeData = parameterSymbol.GetAttributes().SingleOrDefault(ad => ad.AttributeClass.Implements(fromMetadataInterfaceTypeSymbol))!; + return attributeData != null; + } + + static bool HasAttributeImplementingFromMetadataInterfaceType(IParameterSymbol parameterSymbol, WellKnownType fromMetadataInterfaceType, WellKnownTypes wellKnownTypes) { - var fromMetadataInterfaceTypeSymbol = wellKnownTypes.Get(fromMetadataInterfaceType); - return parameter.GetAttributes().Any(ad => ad.AttributeClass.Implements(fromMetadataInterfaceTypeSymbol)); + return TryGetFromMetadataInterfaceTypeAttributeData(parameterSymbol, fromMetadataInterfaceType, wellKnownTypes, out var _); } static bool ReportFromAttributeDiagnostic(OperationAnalysisContext context, WellKnownType fromMetadataInterfaceType, WellKnownTypes wellKnownTypes, IParameterSymbol parameter, INamedTypeSymbol parameterTypeSymbol, Location location) diff --git a/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs b/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs index e9bcd6bd8791..1d96e065aab8 100644 --- a/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs +++ b/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs @@ -203,6 +203,66 @@ public class Customer await VerifyCS.VerifyAnalyzerAsync(source, expectedDiagnostic); } + [Fact] + public async Task Route_Parameter_withNameChanged_viaFromRoute_whenNotParsable_Fails() + { + // Arrange + var source = $$""" +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +var webApp = WebApplication.Create(); +webApp.MapGet("/customers/{cust}", ({|#0:[FromRoute(Name = "cust")]Customer customer|}) => {}); + +public class Customer +{ +} +"""; + + var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsableOrBindable) + .WithArguments("customer", "Customer") + .WithLocation(0); + + // Act + await VerifyCS.VerifyAnalyzerAsync(source, expectedDiagnostic); + } + + [Fact] + public async Task Route_Parameter_withNameChanged_viaFromRoute_whenParsable_Works() + { + // Arrange + var source = $$""" +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +var webApp = WebApplication.Create(); +webApp.MapGet("/customers/{cust}", ({|#0:[FromRoute(Name = "cust")]Customer customer|}) => {}); + +public class Customer : IParsable +{ + public static Customer Parse(string s, IFormatProvider provider) + { + if (TryParse(s, provider, out Customer customer)) + { + return customer; + } + else + { + throw new ArgumentException(s); + } + } + + public static bool TryParse(string s, IFormatProvider provider, out Customer result) + { + result = new Customer(); + return true; + } +} +"""; + + // Act + await VerifyCS.VerifyAnalyzerAsync(source); + } + [Fact] public async Task Route_Parameter_withBindAsyncMethodThatReturnsTask_of_T_Fails() { From a6a69a86227722f8ae9da4c856cfd8ab6fad3e66 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Tue, 3 Jan 2023 10:55:46 +1100 Subject: [PATCH 2/8] Added test to cover multiple handler parameters bound to single route parameters. --- ...NonParsableComplexTypesOnParametersTest.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs b/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs index 1d96e065aab8..073f7de5f761 100644 --- a/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs +++ b/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs @@ -226,6 +226,31 @@ public class Customer await VerifyCS.VerifyAnalyzerAsync(source, expectedDiagnostic); } + [Fact] + public async Task Route_Parameter_withTwoReceivingHandlerParameters_Works() + { + // Arrange + var source = $$""" +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +var webApp = WebApplication.Create(); +webApp.MapGet("/customers/{cust}", ([FromRoute(Name = "cust")]Customer customer, [FromRoute(Name = "cust")]string customerKey) => {}); + +public class Customer +{ + public static bool TryParse(string s, IFormatProvider provider, out Customer result) + { + result = new Customer(); + return true; + } +} +"""; + + // Act + await VerifyCS.VerifyAnalyzerAsync(source); + } + [Fact] public async Task Route_Parameter_withNameChanged_viaFromRoute_whenParsable_Works() { @@ -549,7 +574,7 @@ public class Customer } [Fact] - public async Task Handler_Parameter_withWellknownTypes_Works() + public async Task Handler_Parameter_withWellKnownTypes_Works() { // Arrange var source = $$""" From acea179c45475d92fd29c8e49ae9745fc42eaab8 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Tue, 3 Jan 2023 11:41:13 +1100 Subject: [PATCH 3/8] Integrated JamesNK's changes. --- .../App.Ref/src/CompatibilitySuppressions.xml | 6 ++- .../src/CompatibilitySuppressions.xml | 6 ++- .../Infrastructure/RouteParameterHelper.cs | 34 +++++++++++++++++ .../Infrastructure/SymbolExtensions.cs | 6 +-- .../Infrastructure/WellKnownTypes.cs | 1 - .../Infrastructure/MvcDetector.cs | 1 + .../RoutePatternParametersDetector.cs | 23 +----------- .../RouteStringSyntaxDetector.cs | 1 + .../Infrastructure/RouteUsageDetector.cs | 1 + ...llowNonParsableComplexTypesOnParameters.cs | 37 ++----------------- 10 files changed, 55 insertions(+), 61 deletions(-) create mode 100644 src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/RouteParameterHelper.cs rename src/Framework/AspNetCoreAnalyzers/src/Analyzers/{RouteEmbeddedLanguage => }/Infrastructure/SymbolExtensions.cs (90%) diff --git a/src/Framework/App.Ref/src/CompatibilitySuppressions.xml b/src/Framework/App.Ref/src/CompatibilitySuppressions.xml index e2ec12ef3b32..2d1a36843ca4 100644 --- a/src/Framework/App.Ref/src/CompatibilitySuppressions.xml +++ b/src/Framework/App.Ref/src/CompatibilitySuppressions.xml @@ -1,7 +1,11 @@  - + PKV004 net7.0 + + PKV004 + net8.0 + \ No newline at end of file diff --git a/src/Framework/App.Runtime/src/CompatibilitySuppressions.xml b/src/Framework/App.Runtime/src/CompatibilitySuppressions.xml index 056469f78b07..5beb41b8eec0 100644 --- a/src/Framework/App.Runtime/src/CompatibilitySuppressions.xml +++ b/src/Framework/App.Runtime/src/CompatibilitySuppressions.xml @@ -1,7 +1,11 @@  - + PKV0001 net7.0 + + PKV0001 + net8.0 + \ No newline at end of file diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/RouteParameterHelper.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/RouteParameterHelper.cs new file mode 100644 index 000000000000..2e289234ea04 --- /dev/null +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/RouteParameterHelper.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.App.Analyzers.Infrastructure; +using Microsoft.CodeAnalysis; + +namespace Microsoft.AspNetCore.Analyzers.Infrastructure; + +internal static class RouteParameterHelper +{ + public static string ResolveRouteParameterName(ISymbol parameterSymbol, WellKnownTypes wellKnownTypes) + { + var fromRouteMetadata = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromRouteMetadata); + if (!parameterSymbol.HasAttributeImplementingInterface(fromRouteMetadata, out var attributeData)) + { + return parameterSymbol.Name; // No route metadata attribute! + } + + foreach (var namedArgument in attributeData.NamedArguments) + { + if (namedArgument.Key == "Name") + { + var routeParameterNameConstant = namedArgument.Value; + var routeParameterName = (string)routeParameterNameConstant.Value!; + return routeParameterName; // Have attribute & name is specified. + } + } + + return parameterSymbol.Name; // We have the attribute, but name isn't specified! + } +} diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/SymbolExtensions.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/SymbolExtensions.cs similarity index 90% rename from src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/SymbolExtensions.cs rename to src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/SymbolExtensions.cs index 8a23fe3f33fd..6f7802afd664 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/SymbolExtensions.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/SymbolExtensions.cs @@ -7,7 +7,7 @@ using System.Linq; using Microsoft.CodeAnalysis; -namespace Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure; +namespace Microsoft.AspNetCore.Analyzers.Infrastructure; internal static class SymbolExtensions { @@ -26,14 +26,14 @@ public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeT public static bool HasAttributeImplementingInterface(this ISymbol symbol, INamedTypeSymbol interfaceType) { - return HasAttributeImplementingInterface(symbol, interfaceType, out var _); + return symbol.HasAttributeImplementingInterface(interfaceType, out var _); } public static bool HasAttributeImplementingInterface(this ISymbol symbol, INamedTypeSymbol interfaceType, [NotNullWhen(true)] out AttributeData? matchedAttribute) { foreach (var attributeData in symbol.GetAttributes()) { - if (attributeData.AttributeClass is not null && Implements(attributeData.AttributeClass, interfaceType)) + if (attributeData.AttributeClass is not null && attributeData.AttributeClass.Implements(interfaceType)) { matchedAttribute = attributeData; return true; diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/WellKnownTypes.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/WellKnownTypes.cs index 1fbffd005e5e..4961051a1e0d 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/WellKnownTypes.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/WellKnownTypes.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Threading; using Microsoft.AspNetCore.Analyzers.Infrastructure; -using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure; using Microsoft.CodeAnalysis; namespace Microsoft.AspNetCore.App.Analyzers.Infrastructure; diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/MvcDetector.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/MvcDetector.cs index d438650c76b4..a79fadce149c 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/MvcDetector.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/MvcDetector.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using Microsoft.AspNetCore.Analyzers.Infrastructure; using Microsoft.AspNetCore.App.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RoutePatternParametersDetector.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RoutePatternParametersDetector.cs index dabd6b18c90e..a8704c1bfcb6 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RoutePatternParametersDetector.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RoutePatternParametersDetector.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Immutable; +using Microsoft.AspNetCore.Analyzers.Infrastructure; using Microsoft.AspNetCore.App.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; @@ -31,33 +32,13 @@ static ImmutableArray ResolvedParametersCore(ISymbol symbol, IS } else { - var routeParameterName = ResolveRouteParameterName(child, wellKnownTypes); + var routeParameterName = RouteParameterHelper.ResolveRouteParameterName(child, wellKnownTypes); resolvedParameterSymbols.Add(new ParameterSymbol(routeParameterName, child, topLevelSymbol)); } } return resolvedParameterSymbols.ToImmutable(); } - static string ResolveRouteParameterName(ISymbol parameterSymbol, WellKnownTypes wellKnownTypes) - { - var fromRouteMetadata = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromRouteMetadata); - if (!parameterSymbol.HasAttributeImplementingInterface(fromRouteMetadata, out var attributeData)) - { - return parameterSymbol.Name; // No route metadata attribute! - } - - foreach (var namedArgument in attributeData.NamedArguments) - { - if (namedArgument.Key == "Name") - { - var routeParameterNameConstant = namedArgument.Value; - var routeParameterName = (string)routeParameterNameConstant.Value!; - return routeParameterName; // Have attribute & name is specified. - } - } - - return parameterSymbol.Name; // We have the attribute, but name isn't specified! - } } public static ImmutableArray GetParameterSymbols(ISymbol symbol) diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RouteStringSyntaxDetector.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RouteStringSyntaxDetector.cs index 345336918ea8..f07dd23fb288 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RouteStringSyntaxDetector.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RouteStringSyntaxDetector.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; +using Microsoft.AspNetCore.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RouteUsageDetector.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RouteUsageDetector.cs index 24a5a3010b3f..b9f08b8b58df 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RouteUsageDetector.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RouteUsageDetector.cs @@ -5,6 +5,7 @@ using System.Collections.Immutable; using System.Linq; using System.Threading; +using Microsoft.AspNetCore.Analyzers.Infrastructure; using Microsoft.AspNetCore.Analyzers.Infrastructure.RoutePattern; using Microsoft.AspNetCore.App.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs index 06a346757ead..ebcd062367a4 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs @@ -64,7 +64,7 @@ private static void DisallowNonParsableComplexTypesOnParameters( location )) { continue; } - var routeParameterName = ResolveRouteParameterName(handlerDelegateParameter, wellKnownTypes); + var routeParameterName = RouteParameterHelper.ResolveRouteParameterName(handlerDelegateParameter, wellKnownTypes); // Match handler parameter against route parameters. If it is a route parameter it needs to be parsable/bindable in some fashion. if (routeUsage.RoutePattern.TryGetRouteParameter(routeParameterName, out var routeParameter)) @@ -88,42 +88,11 @@ private static void DisallowNonParsableComplexTypesOnParameters( } } - static string ResolveRouteParameterName(IParameterSymbol parameterSymbol, WellKnownTypes wellKnownTypes) - { - if (!TryGetFromMetadataInterfaceTypeAttributeData(parameterSymbol, WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromRouteMetadata, wellKnownTypes, out var attributeData)) - { - return parameterSymbol.Name; // No route metadata attribute! - } - - foreach (var namedArgument in attributeData.NamedArguments) - { - if (namedArgument.Key == "Name") - { - var routeParameterNameConstant = namedArgument.Value; - var routeParameterName = (string)routeParameterNameConstant.Value!; - return routeParameterName; // Have attribute & name is specified. - } - } - - return parameterSymbol.Name; // We have the attribute, but name isn't specified! - } - - static bool TryGetFromMetadataInterfaceTypeAttributeData(IParameterSymbol parameterSymbol, WellKnownType fromMetadataInterfaceType, WellKnownTypes wellknowTypes, out AttributeData attributeData) - { - var fromMetadataInterfaceTypeSymbol = wellknowTypes.Get(fromMetadataInterfaceType); - attributeData = parameterSymbol.GetAttributes().SingleOrDefault(ad => ad.AttributeClass.Implements(fromMetadataInterfaceTypeSymbol))!; - return attributeData != null; - } - - static bool HasAttributeImplementingFromMetadataInterfaceType(IParameterSymbol parameterSymbol, WellKnownType fromMetadataInterfaceType, WellKnownTypes wellKnownTypes) - { - return TryGetFromMetadataInterfaceTypeAttributeData(parameterSymbol, fromMetadataInterfaceType, wellKnownTypes, out var _); - } - static bool ReportFromAttributeDiagnostic(OperationAnalysisContext context, WellKnownType fromMetadataInterfaceType, WellKnownTypes wellKnownTypes, IParameterSymbol parameter, INamedTypeSymbol parameterTypeSymbol, Location location) { + var fromMetadataInterfaceTypeSymbol = wellKnownTypes.Get(fromMetadataInterfaceType); var parsability = ParsabilityHelper.GetParsability(parameterTypeSymbol, wellKnownTypes); - if (HasAttributeImplementingFromMetadataInterfaceType(parameter, fromMetadataInterfaceType, wellKnownTypes) && parsability != Parsability.Parsable) + if (parameter.HasAttributeImplementingInterface(fromMetadataInterfaceTypeSymbol) && parsability != Parsability.Parsable) { var descriptor = SelectDescriptor(parsability, Bindability.NotBindable); From 3c0dc440e449db47d1e654dd11b5f119c4fdc726 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Tue, 3 Jan 2023 16:20:49 +1100 Subject: [PATCH 4/8] JamesNK feedback. --- .../App.Ref/src/CompatibilitySuppressions.xml | 2 +- .../src/CompatibilitySuppressions.xml | 2 +- .../Infrastructure/RouteParameterHelper.cs | 34 ------------------- .../RoutePattern/RoutePatternTree.cs | 1 + .../Infrastructure/WellKnownTypes.cs | 1 + .../Infrastructure/MvcDetector.cs | 1 - .../RoutePatternParametersDetector.cs | 23 +++++++++++-- .../RouteStringSyntaxDetector.cs | 1 - .../Infrastructure/RouteUsageDetector.cs | 1 - .../Infrastructure/SymbolExtensions.cs | 2 +- ...llowNonParsableComplexTypesOnParameters.cs | 4 ++- 11 files changed, 29 insertions(+), 43 deletions(-) delete mode 100644 src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/RouteParameterHelper.cs rename src/Framework/AspNetCoreAnalyzers/src/Analyzers/{ => RouteEmbeddedLanguage}/Infrastructure/SymbolExtensions.cs (97%) diff --git a/src/Framework/App.Ref/src/CompatibilitySuppressions.xml b/src/Framework/App.Ref/src/CompatibilitySuppressions.xml index 2d1a36843ca4..6aae8c381577 100644 --- a/src/Framework/App.Ref/src/CompatibilitySuppressions.xml +++ b/src/Framework/App.Ref/src/CompatibilitySuppressions.xml @@ -1,5 +1,5 @@  - + PKV004 net7.0 diff --git a/src/Framework/App.Runtime/src/CompatibilitySuppressions.xml b/src/Framework/App.Runtime/src/CompatibilitySuppressions.xml index 5beb41b8eec0..61fb1abb7e35 100644 --- a/src/Framework/App.Runtime/src/CompatibilitySuppressions.xml +++ b/src/Framework/App.Runtime/src/CompatibilitySuppressions.xml @@ -1,5 +1,5 @@  - + PKV0001 net7.0 diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/RouteParameterHelper.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/RouteParameterHelper.cs deleted file mode 100644 index 2e289234ea04..000000000000 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/RouteParameterHelper.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Text; -using Microsoft.AspNetCore.App.Analyzers.Infrastructure; -using Microsoft.CodeAnalysis; - -namespace Microsoft.AspNetCore.Analyzers.Infrastructure; - -internal static class RouteParameterHelper -{ - public static string ResolveRouteParameterName(ISymbol parameterSymbol, WellKnownTypes wellKnownTypes) - { - var fromRouteMetadata = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromRouteMetadata); - if (!parameterSymbol.HasAttributeImplementingInterface(fromRouteMetadata, out var attributeData)) - { - return parameterSymbol.Name; // No route metadata attribute! - } - - foreach (var namedArgument in attributeData.NamedArguments) - { - if (namedArgument.Key == "Name") - { - var routeParameterNameConstant = namedArgument.Value; - var routeParameterName = (string)routeParameterNameConstant.Value!; - return routeParameterName; // Have attribute & name is specified. - } - } - - return parameterSymbol.Name; // We have the attribute, but name isn't specified! - } -} diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/RoutePattern/RoutePatternTree.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/RoutePattern/RoutePatternTree.cs index 88844a842688..bd40589e5fab 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/RoutePattern/RoutePatternTree.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/RoutePattern/RoutePatternTree.cs @@ -5,6 +5,7 @@ using System.Collections.Immutable; using Microsoft.AspNetCore.Analyzers.Infrastructure.EmbeddedSyntax; using Microsoft.AspNetCore.Analyzers.Infrastructure.VirtualChars; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; namespace Microsoft.AspNetCore.Analyzers.Infrastructure.RoutePattern; diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/WellKnownTypes.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/WellKnownTypes.cs index 4961051a1e0d..1fbffd005e5e 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/WellKnownTypes.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/WellKnownTypes.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Threading; using Microsoft.AspNetCore.Analyzers.Infrastructure; +using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure; using Microsoft.CodeAnalysis; namespace Microsoft.AspNetCore.App.Analyzers.Infrastructure; diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/MvcDetector.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/MvcDetector.cs index a79fadce149c..d438650c76b4 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/MvcDetector.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/MvcDetector.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using Microsoft.AspNetCore.Analyzers.Infrastructure; using Microsoft.AspNetCore.App.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RoutePatternParametersDetector.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RoutePatternParametersDetector.cs index a8704c1bfcb6..dabd6b18c90e 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RoutePatternParametersDetector.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RoutePatternParametersDetector.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Immutable; -using Microsoft.AspNetCore.Analyzers.Infrastructure; using Microsoft.AspNetCore.App.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; @@ -32,13 +31,33 @@ static ImmutableArray ResolvedParametersCore(ISymbol symbol, IS } else { - var routeParameterName = RouteParameterHelper.ResolveRouteParameterName(child, wellKnownTypes); + var routeParameterName = ResolveRouteParameterName(child, wellKnownTypes); resolvedParameterSymbols.Add(new ParameterSymbol(routeParameterName, child, topLevelSymbol)); } } return resolvedParameterSymbols.ToImmutable(); } + static string ResolveRouteParameterName(ISymbol parameterSymbol, WellKnownTypes wellKnownTypes) + { + var fromRouteMetadata = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromRouteMetadata); + if (!parameterSymbol.HasAttributeImplementingInterface(fromRouteMetadata, out var attributeData)) + { + return parameterSymbol.Name; // No route metadata attribute! + } + + foreach (var namedArgument in attributeData.NamedArguments) + { + if (namedArgument.Key == "Name") + { + var routeParameterNameConstant = namedArgument.Value; + var routeParameterName = (string)routeParameterNameConstant.Value!; + return routeParameterName; // Have attribute & name is specified. + } + } + + return parameterSymbol.Name; // We have the attribute, but name isn't specified! + } } public static ImmutableArray GetParameterSymbols(ISymbol symbol) diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RouteStringSyntaxDetector.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RouteStringSyntaxDetector.cs index f07dd23fb288..345336918ea8 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RouteStringSyntaxDetector.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RouteStringSyntaxDetector.cs @@ -6,7 +6,6 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; -using Microsoft.AspNetCore.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RouteUsageDetector.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RouteUsageDetector.cs index b9f08b8b58df..24a5a3010b3f 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RouteUsageDetector.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/RouteUsageDetector.cs @@ -5,7 +5,6 @@ using System.Collections.Immutable; using System.Linq; using System.Threading; -using Microsoft.AspNetCore.Analyzers.Infrastructure; using Microsoft.AspNetCore.Analyzers.Infrastructure.RoutePattern; using Microsoft.AspNetCore.App.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/SymbolExtensions.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/SymbolExtensions.cs similarity index 97% rename from src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/SymbolExtensions.cs rename to src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/SymbolExtensions.cs index 6f7802afd664..e80bb5054eff 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/SymbolExtensions.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/SymbolExtensions.cs @@ -7,7 +7,7 @@ using System.Linq; using Microsoft.CodeAnalysis; -namespace Microsoft.AspNetCore.Analyzers.Infrastructure; +namespace Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure; internal static class SymbolExtensions { diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs index ebcd062367a4..9c625fa15b39 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs @@ -64,7 +64,9 @@ private static void DisallowNonParsableComplexTypesOnParameters( location )) { continue; } - var routeParameterName = RouteParameterHelper.ResolveRouteParameterName(handlerDelegateParameter, wellKnownTypes); + + var parameterSymbol = routeUsage.UsageContext.ResolvedParameters.FirstOrDefault(p => p.Symbol.Name == handlerDelegateParameter.Name); + var routeParameterName = parameterSymbol.RouteParameterName ?? handlerDelegateParameter.Name; // Match handler parameter against route parameters. If it is a route parameter it needs to be parsable/bindable in some fashion. if (routeUsage.RoutePattern.TryGetRouteParameter(routeParameterName, out var routeParameter)) From bfaab164782149a3654de892c01bf3895022b7cf Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Tue, 3 Jan 2023 16:26:11 +1100 Subject: [PATCH 5/8] Simplify condition. --- .../DisallowNonParsableComplexTypesOnParameters.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs index 9c625fa15b39..dc3ec84e1242 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs @@ -64,12 +64,8 @@ private static void DisallowNonParsableComplexTypesOnParameters( location )) { continue; } - - var parameterSymbol = routeUsage.UsageContext.ResolvedParameters.FirstOrDefault(p => p.Symbol.Name == handlerDelegateParameter.Name); - var routeParameterName = parameterSymbol.RouteParameterName ?? handlerDelegateParameter.Name; - // Match handler parameter against route parameters. If it is a route parameter it needs to be parsable/bindable in some fashion. - if (routeUsage.RoutePattern.TryGetRouteParameter(routeParameterName, out var routeParameter)) + if (routeUsage.UsageContext.ResolvedParameters.Any(p => p.Symbol.Name == handlerDelegateParameter.Name)) { var parsability = ParsabilityHelper.GetParsability(parameterTypeSymbol, wellKnownTypes); var bindability = ParsabilityHelper.GetBindability(parameterTypeSymbol, wellKnownTypes); From 1c89ed4fef5a1ba72ffa29c1e17abc2f4812666a Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Tue, 3 Jan 2023 17:25:32 +1100 Subject: [PATCH 6/8] Use SymbolEqualityComparer. --- .../DisallowNonParsableComplexTypesOnParameters.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs index dc3ec84e1242..17a621af54e8 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs @@ -65,7 +65,7 @@ private static void DisallowNonParsableComplexTypesOnParameters( )) { continue; } // Match handler parameter against route parameters. If it is a route parameter it needs to be parsable/bindable in some fashion. - if (routeUsage.UsageContext.ResolvedParameters.Any(p => p.Symbol.Name == handlerDelegateParameter.Name)) + if (routeUsage.UsageContext.ResolvedParameters.Any(p => SymbolEqualityComparer.Default.Equals(p.Symbol, handlerDelegateParameter))) { var parsability = ParsabilityHelper.GetParsability(parameterTypeSymbol, wellKnownTypes); var bindability = ParsabilityHelper.GetBindability(parameterTypeSymbol, wellKnownTypes); From fabd5ea73b45a4cb6c3972d8d9fc7f6284635e77 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Wed, 4 Jan 2023 13:27:47 +1100 Subject: [PATCH 7/8] Fix bug and add covering test. --- ...llowNonParsableComplexTypesOnParameters.cs | 14 +++++++++-- ...NonParsableComplexTypesOnParametersTest.cs | 24 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs index 17a621af54e8..0e90cbc31b45 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs @@ -64,8 +64,7 @@ private static void DisallowNonParsableComplexTypesOnParameters( location )) { continue; } - // Match handler parameter against route parameters. If it is a route parameter it needs to be parsable/bindable in some fashion. - if (routeUsage.UsageContext.ResolvedParameters.Any(p => SymbolEqualityComparer.Default.Equals(p.Symbol, handlerDelegateParameter))) + if (IsRouteParameter(routeUsage, handlerDelegateParameter)) { var parsability = ParsabilityHelper.GetParsability(parameterTypeSymbol, wellKnownTypes); var bindability = ParsabilityHelper.GetBindability(parameterTypeSymbol, wellKnownTypes); @@ -86,6 +85,17 @@ private static void DisallowNonParsableComplexTypesOnParameters( } } + static bool IsRouteParameter(RouteUsageModel routeUsage, IParameterSymbol handlerDelegateParameter) + { + // This gets the ParameterSymbol (concept from RouteEmbeddedLanguage) regardless of whether it is in + // the route pattern or not. If it is and it has a custom [FromRoute(Name = "blah")] attribute then + // RouteParameterName and we'll be able to find it by looking it up in the route pattern (thus confirming + // that it is a route parameter). + var resolvedParameter = routeUsage.UsageContext.ResolvedParameters.FirstOrDefault(rp => rp.Symbol.Name == handlerDelegateParameter.Name); + var isRouteParameter = routeUsage.RoutePattern.TryGetRouteParameter(resolvedParameter.RouteParameterName, out var _); + return isRouteParameter; + } + static bool ReportFromAttributeDiagnostic(OperationAnalysisContext context, WellKnownType fromMetadataInterfaceType, WellKnownTypes wellKnownTypes, IParameterSymbol parameter, INamedTypeSymbol parameterTypeSymbol, Location location) { var fromMetadataInterfaceTypeSymbol = wellKnownTypes.Get(fromMetadataInterfaceType); diff --git a/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs b/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs index 073f7de5f761..e834d0df84f1 100644 --- a/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs +++ b/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs @@ -629,5 +629,29 @@ public class MyService // Act await VerifyCS.VerifyAnalyzerAsync(source); } + + [Fact] + public async Task Handler_Parameter_withServiceInterface_Works() + { + // Arrange + var source = $$""" +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Builder; + +var webApp = WebApplication.Create(); +webApp.MapGet("/weatherforecast", (HttpContext context, IDownstreamWebApi downstreamWebApi) => {}); + +// This type doesn't need to be parsable because it should be assumed to be a service type. +public interface IDownstreamWebApi +{ +} +"""; + + // Act + await VerifyCS.VerifyAnalyzerAsync(source); + } + + } From c41b6662fec18e0e57d6f517ea9365c639f0e26d Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Wed, 4 Jan 2023 13:40:56 +1100 Subject: [PATCH 8/8] More test scenarios, abstract base class, interface methods. --- ...NonParsableComplexTypesOnParametersTest.cs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs b/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs index e834d0df84f1..a4bffcbc3a0f 100644 --- a/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs +++ b/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs @@ -652,6 +652,84 @@ public interface IDownstreamWebApi await VerifyCS.VerifyAnalyzerAsync(source); } + [Fact] + public async Task Route_Parameter_withAbstractBaseType_Works() + { + // Arrange + var source = $$""" +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Builder; + +var builder = WebApplication.CreateBuilder(args); +var app = builder.Build(); + +app.MapGet("/", () => "Hello World!"); +app.MapGet("/{customer}", (BaseCustomer customer) => { }); +app.Run(); + +public abstract class BaseCustomer : IParsable +{ + public static BaseCustomer Parse(string s, IFormatProvider? provider) + { + return new CommercialCustomer(); + } + + public static bool TryParse(string? s, IFormatProvider? provider, out BaseCustomer result) + { + result = new CommercialCustomer(); + return true; + } +} + +public class CommercialCustomer : BaseCustomer +{ + +} +"""; + + // Act + await VerifyCS.VerifyAnalyzerAsync(source); + } + [Fact] + public async Task Route_Parameter_withInterfaceType_Works() + { + // Arrange + var source = $$""" +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Builder; + +var builder = WebApplication.CreateBuilder(args); +var app = builder.Build(); + +app.MapGet("/", () => "Hello World!"); +app.MapGet("/{customer}", (ICustomer customer) => { }); +app.Run(); + +public interface ICustomer +{ + public static ICustomer Parse(string s, IFormatProvider? provider) + { + return new CommercialCustomer(); + } + + public static bool TryParse(string? s, IFormatProvider? provider, out ICustomer result) + { + result = new CommercialCustomer(); + return true; + } +} + +public class CommercialCustomer : ICustomer +{ + +} +"""; + + // Act + await VerifyCS.VerifyAnalyzerAsync(source); + } }