@@ -18,8 +18,10 @@ namespace Microsoft.AspNetCore.Analyzers.Mvc;
18
18
19
19
public partial class MvcAnalyzer
20
20
{
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 )
22
22
{
23
+ var controllerHasActionReplacement = controllerRoutePattern != null ? HasActionReplacementToken ( controllerRoutePattern ) : false ;
24
+
23
25
// Ambiguous action route detection is conservative in what it detects to avoid false positives.
24
26
//
25
27
// Successfully matched action routes must:
@@ -31,36 +33,76 @@ private static void DetectAmbiguousActionRoutes(SymbolAnalysisContext context, W
31
33
{
32
34
// Group action routes together. When multiple match in a group, then report action routes to diagnostics.
33
35
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 ) ) ;
35
37
36
- foreach ( var ambigiousGroup in groupedByParent . Where ( g => g . Count ( ) >= 2 ) )
38
+ foreach ( var ambiguousGroup in groupedByParent . Where ( g => g . Count ( ) >= 2 ) )
37
39
{
38
- foreach ( var ambigiousActionRoute in ambigiousGroup )
40
+ foreach ( var ambiguousActionRoute in ambiguousGroup )
39
41
{
40
42
context . ReportDiagnostic ( Diagnostic . Create (
41
43
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
+ }
44
70
}
45
71
}
46
72
}
73
+
74
+ return false ;
47
75
}
48
76
49
77
private readonly struct ActionRouteGroupKey : IEquatable < ActionRouteGroupKey >
50
78
{
51
79
public IMethodSymbol ActionSymbol { get ; }
52
80
public RoutePatternTree RoutePattern { get ; }
53
81
public ImmutableArray < string > HttpMethods { get ; }
82
+ public string ActionName { get ; }
83
+ public bool HasActionReplacement { get ; }
54
84
private readonly WellKnownTypes _wellKnownTypes ;
55
85
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 )
57
87
{
58
88
Debug . Assert ( ! httpMethods . IsDefault ) ;
59
89
60
90
ActionSymbol = actionSymbol ;
61
91
RoutePattern = routePattern ;
62
92
HttpMethods = httpMethods ;
63
93
_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 ;
64
106
}
65
107
66
108
public override bool Equals ( object obj )
@@ -76,6 +118,7 @@ public bool Equals(ActionRouteGroupKey other)
76
118
{
77
119
return
78
120
AmbiguousRoutePatternComparer . Instance . Equals ( RoutePattern , other . RoutePattern ) &&
121
+ ( ! HasActionReplacement || string . Equals ( ActionName , other . ActionName , StringComparison . OrdinalIgnoreCase ) ) &&
79
122
HasMatchingHttpMethods ( HttpMethods , other . HttpMethods ) &&
80
123
CanMatchActions ( _wellKnownTypes , ActionSymbol , other . ActionSymbol ) ;
81
124
}
0 commit comments