-
Notifications
You must be signed in to change notification settings - Fork 10.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Greater than 1 FromBody analyzer. (#46494)
Report an error daignostic when a minimal API ```Map...``` method contains multiple `[FromBody]` attributes or a type referenced with an `[AsPraameters]` attribute with multiple `[FromBody]` members is present.
- Loading branch information
1 parent
0134f61
commit 86e3a4b
Showing
6 changed files
with
258 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/AtMostOneFromBodyAttribute.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Collections.Generic; | ||
using System.Linq; | ||
using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure; | ||
using Microsoft.AspNetCore.App.Analyzers.Infrastructure; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
|
||
namespace Microsoft.AspNetCore.Analyzers.RouteHandlers; | ||
|
||
using WellKnownType = WellKnownTypeData.WellKnownType; | ||
|
||
public partial class RouteHandlerAnalyzer : DiagnosticAnalyzer | ||
{ | ||
private static void AtMostOneFromBodyAttribute( | ||
in OperationAnalysisContext context, | ||
WellKnownTypes wellKnownTypes, | ||
IMethodSymbol methodSymbol) | ||
{ | ||
var fromBodyMetadataInterfaceType = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromBodyMetadata); | ||
var asParametersAttributeType = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_AsParametersAttribute); | ||
|
||
var asParametersDecoratedParameters = methodSymbol.Parameters.Where(p => p.HasAttribute(asParametersAttributeType)); | ||
|
||
foreach (var asParameterDecoratedParameter in asParametersDecoratedParameters) | ||
{ | ||
var fromBodyMetadataInterfaceMembers = asParameterDecoratedParameter.Type.GetMembers().Where( | ||
m => m.HasAttributeImplementingInterface(fromBodyMetadataInterfaceType) | ||
); | ||
|
||
if (fromBodyMetadataInterfaceMembers.Count() >= 2) | ||
{ | ||
ReportDiagnostics(context, fromBodyMetadataInterfaceMembers); | ||
} | ||
} | ||
|
||
var fromBodyMetadataInterfaceParameters = methodSymbol.Parameters.Where(p => p.HasAttributeImplementingInterface(fromBodyMetadataInterfaceType)); | ||
|
||
if (fromBodyMetadataInterfaceParameters.Count() >= 2) | ||
{ | ||
ReportDiagnostics(context, fromBodyMetadataInterfaceParameters); | ||
} | ||
|
||
static void ReportDiagnostics(OperationAnalysisContext context, IEnumerable<ISymbol> symbols) | ||
{ | ||
foreach (var symbol in symbols) | ||
{ | ||
if (symbol.DeclaringSyntaxReferences.Length > 0) | ||
{ | ||
var syntax = symbol.DeclaringSyntaxReferences[0].GetSyntax(context.CancellationToken); | ||
var location = syntax.GetLocation(); | ||
context.ReportDiagnostic(Diagnostic.Create( | ||
DiagnosticDescriptors.AtMostOneFromBodyAttribute, | ||
location | ||
)); | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
173 changes: 173 additions & 0 deletions
173
src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/AtMostOneFromBodyAttributeTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Security.Policy; | ||
using Microsoft.CodeAnalysis.Testing; | ||
using VerifyCS = Microsoft.AspNetCore.Analyzers.Verifiers.CSharpAnalyzerVerifier<Microsoft.AspNetCore.Analyzers.RouteHandlers.RouteHandlerAnalyzer>; | ||
|
||
namespace Microsoft.AspNetCore.Analyzers.RouteHandlers; | ||
|
||
public partial class AtMostOneFromBodyAttributeTest | ||
{ | ||
private TestDiagnosticAnalyzerRunner Runner { get; } = new(new RouteHandlerAnalyzer()); | ||
|
||
[Fact] | ||
public async Task Handler_With_No_FromBody_Attributes_Works() | ||
{ | ||
// Arrange | ||
var source = @" | ||
using Microsoft.AspNetCore.Builder; | ||
var webApp = WebApplication.Create(); | ||
webApp.MapPost(""/products/{productId}"", (string productId, Product product) => {}); | ||
public class Product | ||
{ | ||
} | ||
"; | ||
|
||
// Act | ||
await VerifyCS.VerifyAnalyzerAsync(source); | ||
} | ||
|
||
[Fact] | ||
public async Task Handler_With_One_FromBody_Attributes_Works() | ||
{ | ||
// Arrange | ||
var source = @" | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.AspNetCore.Builder; | ||
var webApp = WebApplication.Create(); | ||
webApp.MapPost(""/products/{productId}"", (string productId, [FromBody]Product product) => {}); | ||
public class Product | ||
{ | ||
} | ||
"; | ||
|
||
// Act | ||
await VerifyCS.VerifyAnalyzerAsync(source); | ||
} | ||
|
||
[Fact] | ||
public async Task Handler_With_Two_FromBody_Attributes_Fails() | ||
{ | ||
// Arrange | ||
var source = @" | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.AspNetCore.Builder; | ||
var webApp = WebApplication.Create(); | ||
webApp.MapPost(""/products/{productId}"", (string productId, {|#0:[FromBody]Product product1|}, {|#1:[FromBody]Product product2|}) => {}); | ||
public class Product | ||
{ | ||
} | ||
"; | ||
|
||
var expectedDiagnostic1 = new DiagnosticResult(DiagnosticDescriptors.AtMostOneFromBodyAttribute).WithLocation(0); | ||
var expectedDiagnostic2 = new DiagnosticResult(DiagnosticDescriptors.AtMostOneFromBodyAttribute).WithLocation(1); | ||
|
||
// Act | ||
await VerifyCS.VerifyAnalyzerAsync( | ||
source, | ||
expectedDiagnostic1, | ||
expectedDiagnostic2 | ||
); | ||
} | ||
|
||
[Fact] | ||
public async Task MethodGroup_Handler_With_Two_FromBody_Attributes_Fails() | ||
{ | ||
// Arrange | ||
var source = @" | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.AspNetCore.Builder; | ||
var webApp = WebApplication.Create(); | ||
webApp.MapPost(""/products/{productId}"", MyHandlers.ProcessRequest); | ||
public static class MyHandlers | ||
{ | ||
public static void ProcessRequest(string productId, {|#0:[FromBody]Product product1|}, {|#1:[FromBody]Product product2|}) | ||
{ | ||
} | ||
} | ||
public class Product | ||
{ | ||
} | ||
"; | ||
|
||
var expectedDiagnostic1 = new DiagnosticResult(DiagnosticDescriptors.AtMostOneFromBodyAttribute).WithLocation(0); | ||
var expectedDiagnostic2 = new DiagnosticResult(DiagnosticDescriptors.AtMostOneFromBodyAttribute).WithLocation(1); | ||
|
||
// Act | ||
await VerifyCS.VerifyAnalyzerAsync( | ||
source, | ||
expectedDiagnostic1, | ||
expectedDiagnostic2 | ||
); | ||
} | ||
|
||
[Fact] | ||
public async Task Handler_Handler_With_AsParameters_Argument_With_TwoFromBody_Attributes_Fails() | ||
{ | ||
// Arrange | ||
var source = @" | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.AspNetCore.Builder; | ||
var webApp = WebApplication.Create(); | ||
webApp.MapPost(""/products/{productId}"", ([AsParameters]GetProductRequest request) => {}); | ||
public class GetProductRequest | ||
{ | ||
{|#0:[FromBody] | ||
public Product Product1 { get; set; }|} | ||
{|#1:[FromBody] | ||
public Product Product2 { get; set; }|} | ||
} | ||
public class Product | ||
{ | ||
} | ||
"; | ||
|
||
var expectedDiagnostic1 = new DiagnosticResult(DiagnosticDescriptors.AtMostOneFromBodyAttribute).WithLocation(0); | ||
var expectedDiagnostic2 = new DiagnosticResult(DiagnosticDescriptors.AtMostOneFromBodyAttribute).WithLocation(1); | ||
|
||
// Act | ||
await VerifyCS.VerifyAnalyzerAsync( | ||
source, | ||
expectedDiagnostic1, | ||
expectedDiagnostic2 | ||
); | ||
} | ||
|
||
[Fact] | ||
public async Task Handler_Handler_With_AsParameters_Argument_With_OneFromBody_Attributes_Works() | ||
{ | ||
// Arrange | ||
var source = @" | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.AspNetCore.Builder; | ||
var webApp = WebApplication.Create(); | ||
webApp.MapPost(""/products/{productId}"", ([AsParameters]GetProductRequest request) => {}); | ||
public class GetProductRequest | ||
{ | ||
{|#0:[FromBody] | ||
public Product Product1 { get; set; }|} | ||
public Product Product2 { get; set; } | ||
} | ||
public class Product | ||
{ | ||
} | ||
"; | ||
|
||
// Act | ||
await VerifyCS.VerifyAnalyzerAsync(source); | ||
} | ||
} |