Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ void AnalyzeResponseExpression(IReturnOperation returnOperation)
internal static ActualApiResponseMetadata?[] InspectReturnOperation(
in ApiControllerSymbolCache symbolCache,
IReturnOperation returnOperation,
ISwitchExpressionArmOperation? armOperation = null)
IOperation? overrideReturnedValue = null)
{
var returnedValue = armOperation?.Value ?? returnOperation.ReturnedValue;
var returnedValue = overrideReturnedValue ?? returnOperation.ReturnedValue;
var defaultStatusCodeAttributeSymbol = symbolCache.DefaultStatusCodeAttribute;

if (returnedValue is null || returnedValue is IInvalidOperation)
Expand Down Expand Up @@ -106,13 +106,21 @@ void AnalyzeResponseExpression(IReturnOperation returnOperation)
for (var i = 0; i < switchExpression.Arms.Length; i++)
{
var arm = switchExpression.Arms[i];
var armMetadata = InspectReturnOperation(symbolCache, returnOperation, arm);
var armMetadata = InspectReturnOperation(symbolCache, returnOperation, arm.Value);
metadata.AddRange(armMetadata);
}

return metadata.ToArray();
}

if (returnedValue is IConditionalOperation conditionalOperation)
{
var metadata = new List<ActualApiResponseMetadata?>();
metadata.AddRange(InspectReturnOperation(symbolCache, returnOperation, conditionalOperation.WhenTrue));
metadata.AddRange(InspectReturnOperation(symbolCache, returnOperation, conditionalOperation.WhenFalse));
return metadata.ToArray();
}

ITypeSymbol? returnType = null;
switch (returnedValue)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,64 @@ public IActionResult Get(int id)
Assert.Null(metadata);
}

[Fact]
public async Task InspectReturnExpression_RecursesIntoConditionalExpression()
{
// Arrange & Act
var source = @"
using Microsoft.AspNetCore.Mvc;

namespace Microsoft.AspNetCore.Mvc.Api.Analyzers
{
[ApiController]
public class TestController : ControllerBase
{
public IActionResult Get(int id)
{
return id == 0 ? NotFound() : Ok();
}
}
}";
var actualResponseMetadata = await RunInspectReturnStatementSyntax(source, "TestController");

// Assert
Assert.Equal(2, actualResponseMetadata.Length);
Assert.NotNull(actualResponseMetadata[0]);
Assert.Equal(404, actualResponseMetadata[0].Value.StatusCode);
Assert.NotNull(actualResponseMetadata[1]);
Assert.Equal(200, actualResponseMetadata[1].Value.StatusCode);
}

[Fact]
public async Task InspectReturnExpression_RecursesIntoNestedConditionalExpression()
{
// Arrange & Act
var source = @"
using Microsoft.AspNetCore.Mvc;

namespace Microsoft.AspNetCore.Mvc.Api.Analyzers
{
[ApiController]
public class TestController : ControllerBase
{
public IActionResult Get(int id)
{
return id == 0 ? NotFound() : id == 1 ? BadRequest() : Ok();
}
}
}";
var actualResponseMetadata = await RunInspectReturnStatementSyntax(source, "TestController");

// Assert
Assert.Equal(3, actualResponseMetadata.Length);
Assert.NotNull(actualResponseMetadata[0]);
Assert.Equal(404, actualResponseMetadata[0].Value.StatusCode);
Assert.NotNull(actualResponseMetadata[1]);
Assert.Equal(400, actualResponseMetadata[1].Value.StatusCode);
Assert.NotNull(actualResponseMetadata[2]);
Assert.Equal(200, actualResponseMetadata[2].Value.StatusCode);
}

[Theory]
[InlineData(ReturnOperationTestVariant.Default)]
[InlineData(ReturnOperationTestVariant.SwitchExpression)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,17 @@ public Task NoDiagnosticsAreReturned_ForOkResultReturningAction()
=> RunNoDiagnosticsAreReturned();

[Fact]
public Task NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred()
=> RunNoDiagnosticsAreReturned();
public async Task DiagnosticsAreReturned_ForApiController_IfConditionalExpressionReturnsUndocumentedStatusCodes()
{
// Arrange
var testSource = MvcTestSource.Read(GetType().Name, "DiagnosticsAreReturned_ForApiController_IfConditionalExpressionReturnsUndocumentedStatusCodes");

// Act
var result = await Executor.GetDiagnosticsAsync(testSource.Source);

// Assert
Assert.Contains(result, d => d.Id == ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode.Id);
}

[Fact]
public Task NoDiagnosticsAreReturned_ForReturnStatementsInLambdas()
Expand Down Expand Up @@ -171,6 +180,117 @@ public IActionResult Get(bool b)
Assert.Contains(result, d => d.Id == ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode.Id);
}

[Fact]
public async Task DiagnosticsAreReturned_ForActionsReturnedFromConditionalExpression()
{
// Arrange
var source = @"
using Microsoft.AspNetCore.Mvc;

namespace Test
{
[ApiController]
public class Foo : ControllerBase
{
[ProducesResponseType(typeof(string), 200)]
public IActionResult Get(int id)
{
return id == 0 ? NotFound() : Ok();
}
}
}";
var testSource = TestSource.Read(source);

// Act
var result = await Executor.GetDiagnosticsAsync(testSource.Source);

// Assert
Assert.Contains(result, d => d.Id == ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode.Id);
}

[Fact]
public async Task DiagnosticsAreReturned_ForActionsReturnedFromNestedConditionalExpression()
{
// Arrange
var source = @"
using Microsoft.AspNetCore.Mvc;

namespace Test
{
[ApiController]
public class Foo : ControllerBase
{
[ProducesResponseType(typeof(string), 200)]
public IActionResult Get(int id)
{
return id == 0 ? NotFound() : id == 1 ? BadRequest() : Ok();
}
}
}";
var testSource = TestSource.Read(source);

// Act
var result = await Executor.GetDiagnosticsAsync(testSource.Source);

// Assert
Assert.Contains(result, d => d.Id == ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode.Id);
}

[Fact]
public async Task NoDiagnosticsAreReturned_ForConditionalExpressionWithAllDocumentedStatusCodes()
{
// Arrange
var source = @"
using Microsoft.AspNetCore.Mvc;

namespace Test
{
[ApiController]
public class Foo : ControllerBase
{
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public IActionResult Get(int id)
{
return id == 0 ? NotFound() : Ok();
}
}
}";
var testSource = TestSource.Read(source);

// Act
var result = await Executor.GetDiagnosticsAsync(testSource.Source);

// Assert
Assert.DoesNotContain(result, d => d.Id == ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode.Id);
}

[Fact]
public async Task DiagnosticsAreReturned_ForExpressionBodiedMemberWithConditionalExpression()
{
// Arrange
var source = @"
using Microsoft.AspNetCore.Mvc;

namespace Test
{
[ApiController]
public class Foo : ControllerBase
{
[ProducesResponseType(typeof(string), 200)]
public IActionResult Get(int id)
=> id == 0 ? NotFound() : Ok();
}
}";
var testSource = TestSource.Read(source);

// Act
var result = await Executor.GetDiagnosticsAsync(testSource.Source);

// Assert
Assert.Contains(result, d => d.Id == ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode.Id);
}

[Fact]
public async Task DiagnosticsAreReturned_ForActionsReturnedFromSwitchStatement()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.Mvc.Api.Analyzers
{
[ApiController]
public class DiagnosticsAreReturned_ForApiController_IfConditionalExpressionReturnsUndocumentedStatusCodes : ControllerBase
{
[ProducesResponseType(201)]
public IActionResult Method(int id)
{
return id == 0 ? /*MM*/(IActionResult)NotFound() : Ok();
}
}
}

This file was deleted.

Loading