Exemplo n.º 1
0
        public async Task IsApiControllerAction_ReturnsFalse_IfMethodReturnTypeIsInvalid()
        {
            // Arrange
            var source      = @"
using Microsoft.AspNetCore.Mvc;

namespace TestNamespace
{
    [ApiController]
    public class TestController : ControllerBase
    {
        public DoesNotExist Get(int id)
        {
            if (id == 0)
            {
                return NotFound();
            }

            return new DoesNotExist(id);
        }
    }
}";
            var project     = DiagnosticProject.Create(GetType().Assembly, new[] { source });
            var compilation = await project.GetCompilationAsync();

            var symbolCache = new ApiControllerSymbolCache(compilation);
            var method      = (IMethodSymbol)compilation.GetTypeByMetadataName("TestNamespace.TestController").GetMembers("Get").First();

            // Act
            var result = ApiControllerFacts.IsApiControllerAction(symbolCache, method);

            // Assert
            Assert.False(result);
        }
        public async Task GetResponseMetadata_ReturnsValuesFromApiConventionMethodAttribute()
        {
            // Arrange
            var compilation = await GetResponseMetadataCompilation();

            var controller  = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}");
            var method      = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.GetResponseMetadata_ReturnsValuesFromApiConventionMethodAttribute)).First();
            var symbolCache = new ApiControllerSymbolCache(compilation);

            // Act
            var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);

            // Assert
            Assert.Collection(
                result,
                metadata =>
            {
                Assert.Equal(200, metadata.StatusCode);
                Assert.NotNull(metadata.Attribute);
            },
                metadata =>
            {
                Assert.Equal(404, metadata.StatusCode);
                Assert.NotNull(metadata.Attribute);
            },
                metadata =>
            {
                Assert.True(metadata.IsDefault);
            });
        }
        internal static IReadOnlyList <ITypeSymbol> GetConventionTypes(ApiControllerSymbolCache symbolCache, IMethodSymbol method)
        {
            var attributes = method.ContainingType.GetAttributes(symbolCache.ApiConventionTypeAttribute).ToArray();

            if (attributes.Length == 0)
            {
                attributes = method.ContainingAssembly.GetAttributes(symbolCache.ApiConventionTypeAttribute).ToArray();
            }

            var conventionTypes = new List <ITypeSymbol>();

            for (var i = 0; i < attributes.Length; i++)
            {
                var attribute = attributes[i];
                if (attribute.ConstructorArguments.Length != 1 ||
                    attribute.ConstructorArguments[0].Kind != TypedConstantKind.Type ||
                    !(attribute.ConstructorArguments[0].Value is ITypeSymbol conventionType))
                {
                    continue;
                }

                conventionTypes.Add(conventionType);
            }

            return(conventionTypes);
        }
Exemplo n.º 4
0
        public static bool IsApiControllerAction(ApiControllerSymbolCache symbolCache, IMethodSymbol method)
        {
            if (method == null)
            {
                return(false);
            }

            if (method.ReturnsVoid || method.ReturnType.TypeKind == TypeKind.Error)
            {
                return(false);
            }

            if (!MvcFacts.IsController(method.ContainingType, symbolCache.ControllerAttribute, symbolCache.NonControllerAttribute))
            {
                return(false);
            }

            if (!method.ContainingType.HasAttribute(symbolCache.IApiBehaviorMetadata, inherit: true))
            {
                return(false);
            }

            if (!MvcFacts.IsControllerAction(method, symbolCache.NonActionAttribute, symbolCache.IDisposableDispose))
            {
                return(false);
            }

            return(true);
        }
        private static IMethodSymbol GetMethodFromConventionMethodAttribute(ApiControllerSymbolCache symbolCache, IMethodSymbol method)
        {
            var attribute = method.GetAttributes(symbolCache.ApiConventionMethodAttribute, inherit: true)
                            .FirstOrDefault();

            if (attribute == null)
            {
                return(null);
            }

            if (attribute.ConstructorArguments.Length != 2)
            {
                return(null);
            }

            if (attribute.ConstructorArguments[0].Kind != TypedConstantKind.Type ||
                !(attribute.ConstructorArguments[0].Value is ITypeSymbol conventionType))
            {
                return(null);
            }

            if (attribute.ConstructorArguments[1].Kind != TypedConstantKind.Primitive ||
                !(attribute.ConstructorArguments[1].Value is string conventionMethodName))
            {
                return(null);
            }

            var conventionMethod = conventionType.GetMembers(conventionMethodName)
                                   .FirstOrDefault(m => m.Kind == SymbolKind.Method && m.IsStatic && m.DeclaredAccessibility == Accessibility.Public);

            return((IMethodSymbol)conventionMethod);
        }
Exemplo n.º 6
0
 public CodeActionContext(
     SemanticModel semanticModel,
     ApiControllerSymbolCache symbolCache,
     IMethodSymbol method,
     MethodDeclarationSyntax methodSyntax,
     CancellationToken cancellationToken)
 {
     SemanticModel     = semanticModel;
     SymbolCache       = symbolCache;
     Method            = method;
     MethodSyntax      = methodSyntax;
     CancellationToken = cancellationToken;
 }
 public CodeActionContext(SemanticModel semanticModel,
                          ApiControllerSymbolCache symbolCache,
                          IMethodSymbol method,
                          MethodDeclarationSyntax methodSyntax,
                          Dictionary <int, string> statusCodeConstants,
                          CancellationToken cancellationToken)
 {
     SemanticModel       = semanticModel;
     SymbolCache         = symbolCache;
     Method              = method;
     MethodSyntax        = methodSyntax;
     StatusCodeConstants = statusCodeConstants;
     CancellationToken   = cancellationToken;
 }
        internal static bool IsMatch(ApiControllerSymbolCache symbolCache, IMethodSymbol method, IMethodSymbol conventionMethod)
        {
            return(MethodMatches() && ParametersMatch());

            bool MethodMatches()
            {
                var methodNameMatchBehavior = GetNameMatchBehavior(symbolCache, conventionMethod);

                if (!IsNameMatch(method.Name, conventionMethod.Name, methodNameMatchBehavior))
                {
                    return(false);
                }

                return(true);
            }

            bool ParametersMatch()
            {
                var methodParameters           = method.Parameters;
                var conventionMethodParameters = conventionMethod.Parameters;

                for (var i = 0; i < conventionMethodParameters.Length; i++)
                {
                    var conventionParameter = conventionMethodParameters[i];
                    if (conventionParameter.IsParams)
                    {
                        return(true);
                    }

                    if (methodParameters.Length <= i)
                    {
                        return(false);
                    }

                    var nameMatchBehavior = GetNameMatchBehavior(symbolCache, conventionParameter);
                    var typeMatchBehavior = GetTypeMatchBehavior(symbolCache, conventionParameter);

                    if (!IsTypeMatch(methodParameters[i].Type, conventionParameter.Type, typeMatchBehavior) ||
                        !IsNameMatch(methodParameters[i].Name, conventionParameter.Name, nameMatchBehavior))
                    {
                        return(false);
                    }
                }

                // Ensure convention has at least as many parameters as the method. params convention argument are handled
                // inside the for loop.
                return(methodParameters.Length == conventionMethodParameters.Length);
            }
        }
        internal static SymbolApiConventionTypeMatchBehavior GetTypeMatchBehavior(ApiControllerSymbolCache symbolCache, ISymbol symbol)
        {
            var attribute = symbol.GetAttributes(symbolCache.ApiConventionTypeMatchAttribute).FirstOrDefault();

            if (attribute == null ||
                attribute.ConstructorArguments.Length != 1 ||
                attribute.ConstructorArguments[0].Kind != TypedConstantKind.Enum)
            {
                return(SymbolApiConventionTypeMatchBehavior.AssignableFrom);
            }

            var intValue = (int)attribute.ConstructorArguments[0].Value;

            return((SymbolApiConventionTypeMatchBehavior)intValue);
        }
Exemplo n.º 10
0
        public async Task IsApiControllerAction_ReturnsTrue_ForValidActionMethods()
        {
            // Arrange
            var compilation = await GetCompilation();

            Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));
            var type   = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerTest_Valid).FullName);
            var method = (IMethodSymbol)type.GetMembers(nameof(ApiConventionAnalyzerTest_Valid.Index)).First();

            // Act
            var result = ApiControllerFacts.IsApiControllerAction(symbolCache, method);

            // Assert
            Assert.True(result);
        }
Exemplo n.º 11
0
        public async Task IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly()
        {
            // Arrange
            var compilation = await GetCompilation(nameof(IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly));

            var symbolCache = new ApiControllerSymbolCache(compilation);
            var type        = compilation.GetTypeByMetadataName(typeof(IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssemblyController).FullName);
            var method      = (IMethodSymbol)type.GetMembers(nameof(IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssemblyController.Action)).First();

            // Act
            var result = ApiControllerFacts.IsApiControllerAction(symbolCache, method);

            // Assert
            Assert.True(result);
        }
Exemplo n.º 12
0
        public async Task IsApiControllerAction_ReturnsFalse_IfContainingTypeIsNotAction()
        {
            // Arrange
            var compilation = await GetCompilation();

            var symbolCache = new ApiControllerSymbolCache(compilation);
            var type        = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerTest_NotAction).FullName);
            var method      = (IMethodSymbol)type.GetMembers(nameof(ApiConventionAnalyzerTest_NotAction.Index)).First();

            // Act
            var result = ApiControllerFacts.IsApiControllerAction(symbolCache, method);

            // Assert
            Assert.False(result);
        }
Exemplo n.º 13
0
        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

            context.RegisterCompilationStartAction(compilationStartAnalysisContext =>
            {
                if (!ApiControllerSymbolCache.TryCreate(compilationStartAnalysisContext.Compilation, out var symbolCache))
                {
                    // No-op if we can't find types we care about.
                    return;
                }

                InitializeWorker(compilationStartAnalysisContext, symbolCache);
            });
        }
Exemplo n.º 14
0
        private async Task <CodeActionContext> CreateCodeActionContext(CancellationToken cancellationToken)
        {
            var root = await _document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var semanticModel = await _document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            var methodReturnStatement = (ReturnStatementSyntax)root.FindNode(_diagnostic.Location.SourceSpan);
            var methodSyntax          = methodReturnStatement.FirstAncestorOrSelf <MethodDeclarationSyntax>();
            var method = semanticModel.GetDeclaredSymbol(methodSyntax, cancellationToken);

            var symbolCache = new ApiControllerSymbolCache(semanticModel.Compilation);

            var codeActionContext = new CodeActionContext(semanticModel, symbolCache, method, methodSyntax, cancellationToken);

            return(codeActionContext);
        }
        public async Task GetNameMatchBehavior_ReturnsExact_WhenNoAttributesArePresent()
        {
            // Arrange
            var expected    = SymbolApiConventionNameMatchBehavior.Exact;
            var compilation = await GetCompilationAsync();

            Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

            var testConvention = compilation.GetTypeByMetadataName(TestConventionName);
            var method         = testConvention.GetMembers(nameof(TestConvention.MethodWithoutMatchBehavior)).First();

            // Act
            var result = GetNameMatchBehavior(symbolCache, method);

            // Assert
            Assert.Equal(expected, result);
        }
        public async Task GetErrorResponseType_ReturnsTypeDefinedAtAction()
        {
            // Arrange
            var compilation = await GetCompilation(nameof(GetErrorResponseType_ReturnsTypeDefinedAtAction));

            var expected = compilation.GetTypeByMetadataName(typeof(GetErrorResponseType_ReturnsTypeDefinedAtActionModel).FullName);

            var type        = compilation.GetTypeByMetadataName(typeof(GetErrorResponseType_ReturnsTypeDefinedAtActionController).FullName);
            var method      = (IMethodSymbol)type.GetMembers("Action").First();
            var symbolCache = new ApiControllerSymbolCache(compilation);

            // Act
            var result = SymbolApiResponseMetadataProvider.GetErrorResponseType(symbolCache, method);

            // Assert
            Assert.Same(expected, result);
        }
        public async Task GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscovered()
        {
            // Arrange
            var compilation = await GetCompilation(nameof(GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscovered));

            var expected = compilation.GetTypeByMetadataName(typeof(ProblemDetails).FullName);

            var type        = compilation.GetTypeByMetadataName(typeof(GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscoveredController).FullName);
            var method      = (IMethodSymbol)type.GetMembers("Action").First();
            var symbolCache = new ApiControllerSymbolCache(compilation);

            // Act
            var result = SymbolApiResponseMetadataProvider.GetErrorResponseType(symbolCache, method);

            // Assert
            Assert.Same(expected, result);
        }
        public async Task GetResponseMetadata_ReturnsEmptySequence_IfNoAttributesArePresent_ForPostAction()
        {
            // Arrange
            var compilation = await GetResponseMetadataCompilation();

            var controller  = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerWithoutConvention)}");
            var method      = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerWithoutConvention.PostPerson)).First();
            var symbolCache = new ApiControllerSymbolCache(compilation);

            // Act
            var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);

            // Assert
            Assert.Collection(
                result,
                metadata => Assert.True(metadata.IsImplicit));
        }
        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

            context.RegisterCompilationStartAction(compilationStartAnalysisContext =>
            {
                var symbolCache = new ApiControllerSymbolCache(compilationStartAnalysisContext.Compilation);
                if (symbolCache.ApiConventionTypeAttribute == null || symbolCache.ApiConventionTypeAttribute.TypeKind == TypeKind.Error)
                {
                    // No-op if we can't find types we care about.
                    return;
                }

                InitializeWorker(compilationStartAnalysisContext, symbolCache);
            });
        }
        public async Task GetNameMatchBehavior_ReturnsValueFromAttributes()
        {
            // Arrange
            var expected    = SymbolApiConventionNameMatchBehavior.Prefix;
            var compilation = await GetCompilationAsync();

            var symbolCache = new ApiControllerSymbolCache(compilation);

            var testConvention = compilation.GetTypeByMetadataName(TestConventionName);
            var method         = testConvention.GetMembers(nameof(TestConvention.Get)).First();

            // Act
            var result = GetNameMatchBehavior(symbolCache, method);

            // Assert
            Assert.Equal(expected, result);
        }
        public async Task GetResponseMetadata_IgnoresProducesAttribute()
        {
            // Arrange
            var compilation = await GetResponseMetadataCompilation();

            var controller  = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}");
            var method      = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesAttribute)).First();
            var symbolCache = new ApiControllerSymbolCache(compilation);

            // Act
            var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);

            // Assert
            Assert.Collection(
                result,
                metadata => Assert.True(metadata.IsImplicit));
        }
        public async Task GetNameMatchBehavior_ReturnsExact_WhenNoNameMatchBehaviorAttributeIsSpecified()
        {
            // Arrange
            var expected    = SymbolApiConventionNameMatchBehavior.Exact;
            var compilation = await GetCompilationAsync();

            var symbolCache = new ApiControllerSymbolCache(compilation);

            var testConvention = compilation.GetTypeByMetadataName(TestConventionName);
            var method         = testConvention.GetMembers(nameof(TestConvention.MethodWithRandomAttributes)).First();

            // Act
            var result = GetNameMatchBehavior(symbolCache, method);

            // Assert
            Assert.Equal(expected, result);
        }
        private async Task RunMatchTest(string methodName, string conventionMethodName, bool expected)
        {
            var compilation = await GetCompilationAsync();

            var symbolCache = new ApiControllerSymbolCache(compilation);

            var testController   = compilation.GetTypeByMetadataName(TestControllerName);
            var testConvention   = compilation.GetTypeByMetadataName(TestConventionName);
            var method           = (IMethodSymbol)testController.GetMembers(methodName).First();
            var conventionMethod = (IMethodSymbol)testConvention.GetMembers(conventionMethodName).First();

            // Act
            var result = IsMatch(symbolCache, method, conventionMethod);

            // Assert
            Assert.Equal(expected, result);
        }
        public async Task GetTypeMatchBehavior_ReturnsIsAssignableFrom_WhenNoMatchingAttributesArePresent()
        {
            // Arrange
            var expected    = SymbolApiConventionTypeMatchBehavior.AssignableFrom;
            var compilation = await GetCompilationAsync();

            Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

            var testConvention = compilation.GetTypeByMetadataName(TestConventionName);
            var method         = (IMethodSymbol)testConvention.GetMembers(nameof(TestConvention.MethodParameterWithRandomAttributes)).First();
            var parameter      = method.Parameters[0];

            // Act
            var result = GetTypeMatchBehavior(symbolCache, parameter);

            // Assert
            Assert.Equal(expected, result);
        }
        public async Task GetTypeMatchBehavior_ReturnsValueFromAttributes()
        {
            // Arrange
            var expected    = SymbolApiConventionTypeMatchBehavior.Any;
            var compilation = await GetCompilationAsync();

            var symbolCache = new ApiControllerSymbolCache(compilation);

            var testConvention = compilation.GetTypeByMetadataName(TestConventionName);
            var method         = (IMethodSymbol)testConvention.GetMembers(nameof(TestConvention.MethodWithAnyTypeMatchBehaviorParameter)).First();
            var parameter      = method.Parameters[0];

            // Act
            var result = GetTypeMatchBehavior(symbolCache, parameter);

            // Assert
            Assert.Equal(expected, result);
        }
        private async Task <(bool result, IList <ActualApiResponseMetadata> responseMetadatas, TestSource testSource)> TryGetActualResponseMetadata(string typeName, string methodName)
        {
            var testSource = MvcTestSource.Read(GetType().Name, "TryGetActualResponseMetadataTests");
            var project    = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source });

            var compilation = await GetCompilation("TryGetActualResponseMetadataTests");

            var type        = compilation.GetTypeByMetadataName(typeName);
            var method      = (IMethodSymbol)type.GetMembers(methodName).First();
            var symbolCache = new ApiControllerSymbolCache(compilation);

            var syntaxTree    = method.DeclaringSyntaxReferences[0].SyntaxTree;
            var methodSyntax  = (MethodDeclarationSyntax)syntaxTree.GetRoot().FindNode(method.Locations[0].SourceSpan);
            var semanticModel = compilation.GetSemanticModel(syntaxTree);

            var result = ActualApiResponseMetadataFactory.TryGetActualResponseMetadata(symbolCache, semanticModel, methodSyntax, CancellationToken.None, out var responseMetadatas);

            return(result, responseMetadatas, testSource);
        }
        private static IList <DeclaredApiResponseMetadata> GetResponseMetadataFromConventions(
            ApiControllerSymbolCache symbolCache,
            IMethodSymbol method,
            IReadOnlyList <ITypeSymbol> conventionTypes)
        {
            var conventionMethod = GetMethodFromConventionMethodAttribute(symbolCache, method);

            if (conventionMethod == null)
            {
                conventionMethod = MatchConventionMethod(symbolCache, method, conventionTypes);
            }

            if (conventionMethod != null)
            {
                return(GetResponseMetadataFromMethodAttributes(symbolCache, conventionMethod));
            }

            return(Array.Empty <DeclaredApiResponseMetadata>());
        }
        private async Task <ActualApiResponseMetadata?> RunInspectReturnStatementSyntax([CallerMemberName] string test = null)
        {
            // Arrange
            var compilation = await GetCompilation("InspectReturnExpressionTests");

            Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

            var controllerType = compilation.GetTypeByMetadataName(typeof(TestFiles.InspectReturnExpressionTests.TestController).FullName);
            var syntaxTree     = controllerType.DeclaringSyntaxReferences[0].SyntaxTree;

            var method          = (IMethodSymbol)Assert.Single(controllerType.GetMembers(test));
            var methodSyntax    = syntaxTree.GetRoot().FindNode(method.Locations[0].SourceSpan);
            var returnStatement = methodSyntax.DescendantNodes().OfType <ReturnStatementSyntax>().First();
            var returnOperation = (IReturnOperation)compilation.GetSemanticModel(syntaxTree).GetOperation(returnStatement);

            return(ActualApiResponseMetadataFactory.InspectReturnOperation(
                       symbolCache,
                       returnOperation));
        }
        private async Task <ActualApiResponseMetadata?> RunInspectReturnStatementSyntax(string source, string test)
        {
            var project     = MvcDiagnosticAnalyzerRunner.CreateProjectWithReferencesInBinDir(GetType().Assembly, new[] { source });
            var compilation = await project.GetCompilationAsync();

            Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

            var returnType = compilation.GetTypeByMetadataName($"{Namespace}.{test}");
            var syntaxTree = returnType.DeclaringSyntaxReferences[0].SyntaxTree;

            var method          = (IMethodSymbol)returnType.GetMembers().First();
            var methodSyntax    = syntaxTree.GetRoot().FindNode(method.Locations[0].SourceSpan);
            var returnStatement = methodSyntax.DescendantNodes().OfType <ReturnStatementSyntax>().First();
            var returnOperation = (IReturnOperation)compilation.GetSemanticModel(syntaxTree).GetOperation(returnStatement);

            return(ActualApiResponseMetadataFactory.InspectReturnOperation(
                       symbolCache,
                       returnOperation));
        }
        private async Task <ActualApiResponseMetadata?> RunInspectReturnStatementSyntax(string source, string test)
        {
            var project     = DiagnosticProject.Create(GetType().Assembly, new[] { source });
            var compilation = await project.GetCompilationAsync();

            var symbolCache = new ApiControllerSymbolCache(compilation);

            var returnType = compilation.GetTypeByMetadataName($"{Namespace}.{test}");
            var syntaxTree = returnType.DeclaringSyntaxReferences[0].SyntaxTree;

            var method          = (IMethodSymbol)returnType.GetMembers().First();
            var methodSyntax    = syntaxTree.GetRoot().FindNode(method.Locations[0].SourceSpan);
            var returnStatement = methodSyntax.DescendantNodes().OfType <ReturnStatementSyntax>().First();

            return(ActualApiResponseMetadataFactory.InspectReturnStatementSyntax(
                       symbolCache,
                       compilation.GetSemanticModel(syntaxTree),
                       returnStatement,
                       CancellationToken.None));
        }