diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/SpecifyRouteAttribute.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/SpecifyRouteAttribute.cs index e4fed7ed06c..7930134d629 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/SpecifyRouteAttribute.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/SpecifyRouteAttribute.cs @@ -26,7 +26,7 @@ namespace SonarAnalyzer.Rules.CSharp; public sealed class SpecifyRouteAttribute : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6934"; - private const string MessageFormat = "Specify the RouteAttribute when an HttpMethodAttribute is specified at an action level"; + private const string MessageFormat = "Specify the RouteAttribute when an HttpMethodAttribute is specified at an action level."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); @@ -49,7 +49,7 @@ protected override void Initialize(SonarAnalysisContext context) => symbolStart.RegisterSyntaxNodeAction(nodeContext => { var node = (MethodDeclarationSyntax)nodeContext.Node; - if (nodeContext.SemanticModel.GetDeclaredSymbol(node, nodeContext.Cancel) is IMethodSymbol method + if (nodeContext.SemanticModel.GetDeclaredSymbol(node, nodeContext.Cancel) is { } method && method.IsControllerMethod() && method.GetAttributes().Any(x => x.AttributeClass.IsAny(KnownType.Microsoft_AspNetCore_Mvc_Routing_HttpMethodAttributes) @@ -67,9 +67,12 @@ private static void ReportIssues(SonarSymbolReportingContext context, ISymbol sy { if (!secondaryLocations.IsEmpty) { - foreach (var declaration in symbol.DeclaringSyntaxReferences.Select(r => r.GetSyntax())) + foreach (var declaration in symbol.DeclaringSyntaxReferences.Select(x => x.GetSyntax())) { - context.ReportIssue(CSharpGeneratedCodeRecognizer.Instance, Diagnostic.Create(Rule, declaration.GetLocation(), secondaryLocations)); + if (declaration.GetIdentifier() is { } identifier) + { + context.ReportIssue(CSharpGeneratedCodeRecognizer.Instance, Diagnostic.Create(Rule, identifier.GetLocation(), secondaryLocations)); + } } } } diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/SpecifyRouteAttribute.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/SpecifyRouteAttribute.cs index a1fdd57df0a..9709401bb37 100644 --- a/analyzers/tests/SonarAnalyzer.Test/TestCases/SpecifyRouteAttribute.cs +++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/SpecifyRouteAttribute.cs @@ -1,38 +1,95 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Routing; -public class NoActionHasHttpAttributeWithRouteController : Controller +using MA = Microsoft.AspNetCore; + +public class RouteTemplateIsNotSpecified : Controller { - public IActionResult Index() => View(); // Compliant + public IActionResult Index() => View(); // Compliant [HttpGet] - public IActionResult Index2() => View(); // Compliant, default behavior if not route template is defined + public IActionResult Index2() => View(); // Compliant + + [HttpGet()] + public IActionResult Index3() => View(); // Compliant + + [HttpGetAttribute] + public IActionResult Index4() => View(); // Compliant + + [Microsoft.AspNetCore.Mvc.HttpGet] + public IActionResult Index5() => View(); // Compliant + + [MA.Mvc.HttpGet] + public IActionResult Index6() => View(); // Compliant - public IActionResult Error() => View(); // Compliant + [method: HttpGet] + public IActionResult Index7() => View(); // Compliant + + public IActionResult Error() => View(); // Compliant } -public class ActionHasHttpAttributeWithRouteController : Controller // Noncompliant [controller] +public class RouteTemplatesAreSpecified : Controller // Noncompliant [controller] {{Specify the RouteAttribute when an HttpMethodAttribute is specified at an action level.}} +// ^^^^^^^^^^^^^^^^^^^^^^^^^^ { - [HttpGet("GetObject")] - public IActionResult Get() => View(); // Secondary [controller] + private const string ConstantRoute = "ConstantRoute"; + + [HttpGet("GetObject")] + public IActionResult Get() => View(); +// ^^^ Secondary [controller] + + [HttpGet("GetFirst")] + [HttpGet("GetSecond")] + public IActionResult GetMultipleTemplates() => View(); // Secondary [controller] + + [HttpGet("GetFirst")] + [HttpPut("GetSecond")] + public IActionResult Mix() => View(); // Secondary [controller] + + [HttpPost("CreateObject")] + public IActionResult Post() => View(); // Secondary [controller] + + [HttpPut("UpdateObject")] + public IActionResult Put() => View(); // Secondary [controller] + + [HttpDelete("DeleteObject")] + public IActionResult Delete() => View(); // Secondary [controller] + + [HttpPatch("PatchObject")] + public IActionResult Patch() => View(); // Secondary [controller] + + [HttpHead("Head")] + public IActionResult HttpHead() => View(); // Secondary [controller] + + [HttpOptions("Options")] + public IActionResult HttpOptions() => View(); // Secondary [controller] + + [Route("details")] + public IActionResult GetDetails() => View(); // FN + + [HttpGet("details", Order = 1)] + public IActionResult GetDetails2() => View(); // Secondary [controller] - [HttpPost("CreateObject")] - public IActionResult Post() => View(); // Secondary [controller] + [HttpGet("details", Order = 1, Name = "Details")] + public IActionResult GetDetails3() => View(); // Secondary [controller] - [HttpPut("UpdateObject")] - public IActionResult Put() => View(); // Secondary [controller] + [HttpGet(ConstantRoute)] + public IActionResult GetDetails4() => View(); // Secondary [controller] - [HttpDelete("DeleteObject")] - public IActionResult Delete() => View(); // Secondary [controller] + [HttpGet(""" + ConstantRoute + """)] + public IActionResult GetDetails5() => View(); // Secondary [controller] - [HttpPatch("PatchObject")] - public IActionResult Patch() => View(); // Secondary [controller] + [HttpGet($"Route {ConstantRoute}")] + public IActionResult GetDetails6() => View(); // Secondary [controller] - [HttpHead("Head")] - public IActionResult HttpHead() => View(); // Secondary [controller] + [HttpGet($""" + {ConstantRoute} + """)] + public IActionResult GetDetails7() => View(); // Secondary [controller] - [HttpOptions("Options")] - public IActionResult HttpOptions() => View(); // Secondary [controller] + // [HttpPost("Comment")] + public IActionResult Comment() => View(); } public class WithUserDefinedAttribute : Controller