Skip to content

Commit 296daad

Browse files
authored
Fix ambiguous route analyzer false positive with action replacement (#51880) (#52034)
1 parent aaa15bd commit 296daad

File tree

3 files changed

+342
-8
lines changed

3 files changed

+342
-8
lines changed

src/Framework/AspNetCoreAnalyzers/src/Analyzers/Mvc/DetectAmbiguousActionRoutes.cs

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ namespace Microsoft.AspNetCore.Analyzers.Mvc;
1818

1919
public partial class MvcAnalyzer
2020
{
21-
private static void DetectAmbiguousActionRoutes(SymbolAnalysisContext context, WellKnownTypes wellKnownTypes, List<ActionRoute> actionRoutes)
21+
private static void DetectAmbiguousActionRoutes(SymbolAnalysisContext context, WellKnownTypes wellKnownTypes, RoutePatternTree? controllerRoutePattern, List<ActionRoute> actionRoutes)
2222
{
23+
var controllerHasActionReplacement = controllerRoutePattern != null ? HasActionReplacementToken(controllerRoutePattern) : false;
24+
2325
// Ambiguous action route detection is conservative in what it detects to avoid false positives.
2426
//
2527
// Successfully matched action routes must:
@@ -31,36 +33,76 @@ private static void DetectAmbiguousActionRoutes(SymbolAnalysisContext context, W
3133
{
3234
// Group action routes together. When multiple match in a group, then report action routes to diagnostics.
3335
var groupedByParent = actionRoutes
34-
.GroupBy(ar => new ActionRouteGroupKey(ar.ActionSymbol, ar.RouteUsageModel.RoutePattern, ar.HttpMethods, wellKnownTypes));
36+
.GroupBy(ar => new ActionRouteGroupKey(ar.ActionSymbol, ar.RouteUsageModel.RoutePattern, ar.HttpMethods, controllerHasActionReplacement, wellKnownTypes));
3537

36-
foreach (var ambigiousGroup in groupedByParent.Where(g => g.Count() >= 2))
38+
foreach (var ambiguousGroup in groupedByParent.Where(g => g.Count() >= 2))
3739
{
38-
foreach (var ambigiousActionRoute in ambigiousGroup)
40+
foreach (var ambiguousActionRoute in ambiguousGroup)
3941
{
4042
context.ReportDiagnostic(Diagnostic.Create(
4143
DiagnosticDescriptors.AmbiguousActionRoute,
42-
ambigiousActionRoute.RouteUsageModel.UsageContext.RouteToken.GetLocation(),
43-
ambigiousActionRoute.RouteUsageModel.RoutePattern.Root.ToString()));
44+
ambiguousActionRoute.RouteUsageModel.UsageContext.RouteToken.GetLocation(),
45+
ambiguousActionRoute.RouteUsageModel.RoutePattern.Root.ToString()));
46+
}
47+
}
48+
}
49+
}
50+
51+
private static bool HasActionReplacementToken(RoutePatternTree routePattern)
52+
{
53+
for (var i = 0; i < routePattern.Root.Parts.Length; i++)
54+
{
55+
if (routePattern.Root.Parts[i] is RoutePatternSegmentNode segment)
56+
{
57+
for (var j = 0; j < segment.Children.Length; j++)
58+
{
59+
if (segment.Children[j] is RoutePatternReplacementNode replacementNode)
60+
{
61+
if (!replacementNode.TextToken.IsMissing)
62+
{
63+
var name = replacementNode.TextToken.Value!.ToString();
64+
if (string.Equals(name, "action", StringComparison.OrdinalIgnoreCase))
65+
{
66+
return true;
67+
}
68+
}
69+
}
4470
}
4571
}
4672
}
73+
74+
return false;
4775
}
4876

4977
private readonly struct ActionRouteGroupKey : IEquatable<ActionRouteGroupKey>
5078
{
5179
public IMethodSymbol ActionSymbol { get; }
5280
public RoutePatternTree RoutePattern { get; }
5381
public ImmutableArray<string> HttpMethods { get; }
82+
public string ActionName { get; }
83+
public bool HasActionReplacement { get; }
5484
private readonly WellKnownTypes _wellKnownTypes;
5585

56-
public ActionRouteGroupKey(IMethodSymbol actionSymbol, RoutePatternTree routePattern, ImmutableArray<string> httpMethods, WellKnownTypes wellKnownTypes)
86+
public ActionRouteGroupKey(IMethodSymbol actionSymbol, RoutePatternTree routePattern, ImmutableArray<string> httpMethods, bool controllerHasActionReplacement, WellKnownTypes wellKnownTypes)
5787
{
5888
Debug.Assert(!httpMethods.IsDefault);
5989

6090
ActionSymbol = actionSymbol;
6191
RoutePattern = routePattern;
6292
HttpMethods = httpMethods;
6393
_wellKnownTypes = wellKnownTypes;
94+
ActionName = GetActionName(ActionSymbol, _wellKnownTypes);
95+
HasActionReplacement = controllerHasActionReplacement || HasActionReplacementToken(RoutePattern);
96+
}
97+
98+
private static string GetActionName(IMethodSymbol actionSymbol, WellKnownTypes wellKnownTypes)
99+
{
100+
var actionNameAttribute = actionSymbol.GetAttributes(wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Mvc_ActionNameAttribute), inherit: true).FirstOrDefault();
101+
if (actionNameAttribute != null && actionNameAttribute.ConstructorArguments.Length > 0 && actionNameAttribute.ConstructorArguments[0].Value is string name)
102+
{
103+
return name;
104+
}
105+
return actionSymbol.Name;
64106
}
65107

66108
public override bool Equals(object obj)
@@ -76,6 +118,7 @@ public bool Equals(ActionRouteGroupKey other)
76118
{
77119
return
78120
AmbiguousRoutePatternComparer.Instance.Equals(RoutePattern, other.RoutePattern) &&
121+
(!HasActionReplacement || string.Equals(ActionName, other.ActionName, StringComparison.OrdinalIgnoreCase)) &&
79122
HasMatchingHttpMethods(HttpMethods, other.HttpMethods) &&
80123
CanMatchActions(_wellKnownTypes, ActionSymbol, other.ActionSymbol);
81124
}

src/Framework/AspNetCoreAnalyzers/src/Analyzers/Mvc/MvcAnalyzer.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
using System.Collections.Concurrent;
66
using System.Collections.Generic;
77
using System.Collections.Immutable;
8+
using System.Linq;
89
using System.Threading;
10+
using Microsoft.AspNetCore.Analyzers.Infrastructure.RoutePattern;
911
using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure;
1012
using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
1113
using Microsoft.CodeAnalysis;
@@ -47,9 +49,20 @@ public override void Initialize(AnalysisContext context)
4749
actionRoutes = new List<ActionRoute>();
4850
}
4951

52+
RoutePatternTree? controllerRoutePattern = null;
53+
var controllerRouteAttribute = namedTypeSymbol.GetAttributes(wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Mvc_RouteAttribute), inherit: true).FirstOrDefault();
54+
if (controllerRouteAttribute != null)
55+
{
56+
var routeUsage = GetRouteUsageModel(controllerRouteAttribute, routeUsageCache, context.CancellationToken);
57+
if (routeUsage != null)
58+
{
59+
controllerRoutePattern = routeUsage.RoutePattern;
60+
}
61+
}
62+
5063
PopulateActionRoutes(context, wellKnownTypes, routeUsageCache, namedTypeSymbol, actionRoutes);
5164

52-
DetectAmbiguousActionRoutes(context, wellKnownTypes, actionRoutes);
65+
DetectAmbiguousActionRoutes(context, wellKnownTypes, controllerRoutePattern, actionRoutes);
5366

5467
// Return to the pool.
5568
actionRoutes.Clear();

0 commit comments

Comments
 (0)