From 72f7e52e1edcb70f265b786f47d5f212d9a27270 Mon Sep 17 00:00:00 2001 From: Joni Date: Thu, 6 Sep 2018 16:04:09 +0000 Subject: [PATCH 1/5] Remove blank line --- .../Internal/PagedBufferedTextWriter.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs index 3c436b3277..4c8b1937c9 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs @@ -39,7 +39,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal var length = _charBuffer.Length; if (length == 0) { - // If nothing sync buffered return CompletedTask, // so we can fast-path skip async state-machine creation return Task.CompletedTask; From 989559392680eac2b92a2acfe198659b10a78f32 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Thu, 6 Sep 2018 11:05:16 -0700 Subject: [PATCH 2/5] Include diagnosticMessages from xunit for Functional tests --- test/Microsoft.AspNetCore.Mvc.FunctionalTests/xunit.runner.json | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/xunit.runner.json b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/xunit.runner.json index 0d8a1f6a45..74f4888c0b 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/xunit.runner.json +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/xunit.runner.json @@ -1,4 +1,5 @@ { + "diagnosticMessages": true, "shadowCopy": false, "longRunningTestSeconds": 60 } From 013697ad89d571665ba08e5d6bae3154ba55c531 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 6 Sep 2018 11:37:47 -0700 Subject: [PATCH 3/5] Remove experimental analyzers --- Mvc.NoFun.sln | 30 -- Mvc.sln | 30 -- .../ActionsMustNotBeAsyncVoidAnalyzer.cs | 56 ---- .../ActionsMustNotBeAsyncVoidFixProvider.cs | 58 ---- .../ApiActionsAreAttributeRoutedAnalyzer.cs | 52 --- ...ApiActionsAreAttributeRoutedFixProvider.cs | 187 ----------- ...ActionsShouldUseActionResultOfTAnalyzer.cs | 102 ------ ...ShouldUseActionResultOfTCodeFixProvider.cs | 55 ---- .../ApiControllerAnalyzerBase.cs | 39 --- .../ApiControllerAnalyzerContext.cs | 64 ---- .../CodeAnalysisExtensions.cs | 78 ----- .../ControllerAnalyzerBase.cs | 39 --- .../ControllerAnalyzerContext.cs | 47 --- .../DiagnosticDescriptors.cs | 46 --- ...pNetCore.Mvc.Analyzers.Experimental.csproj | 29 -- .../TypeNames.cs | 38 --- .../ActionsMustNotBeAsyncVoidFacts.cs | 173 ---------- .../ApiActionsAreAttributeRoutedFacts.cs | 304 ------------------ ...ApiActionsShouldUseActionResultOfTFacts.cs | 261 --------------- .../Infrastructure/AnalyzerTestBase.cs | 182 ----------- ...ore.Mvc.Analyzers.Experimental.Test.csproj | 20 -- .../xunit.runner.json | 3 - version.props | 8 - 23 files changed, 1901 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidAnalyzer.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidFixProvider.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedAnalyzer.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedFixProvider.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTAnalyzer.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTCodeFixProvider.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerBase.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerContext.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/CodeAnalysisExtensions.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerBase.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerContext.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/DiagnosticDescriptors.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/TypeNames.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ActionsMustNotBeAsyncVoidFacts.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsAreAttributeRoutedFacts.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsShouldUseActionResultOfTFacts.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Infrastructure/AnalyzerTestBase.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test.csproj delete mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/xunit.runner.json diff --git a/Mvc.NoFun.sln b/Mvc.NoFun.sln index d8e44d40bf..3995a5463a 100644 --- a/Mvc.NoFun.sln +++ b/Mvc.NoFun.sln @@ -105,12 +105,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{44546170-35BF-448F-88F5-4331AE67AEAE}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test", "test\Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test\Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test.csproj", "{2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Analyzers", "src\Microsoft.AspNetCore.Mvc.Analyzers\Microsoft.AspNetCore.Mvc.Analyzers.csproj", "{30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Analyzers.Experimental", "src\Microsoft.AspNetCore.Mvc.Analyzers.Experimental\Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj", "{F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc.Analyzers.Test", "test\Mvc.Analyzers.Test\Mvc.Analyzers.Test.csproj", "{829D9A67-2D07-4CE6-86C0-59F2549B0CFA}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Views.TestCommon", "test\Microsoft.AspNetCore.Mvc.Views.TestCommon\Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj", "{0772E545-A674-4165-9469-E3D79D88A4A8}" @@ -507,18 +503,6 @@ Global {28D4DA20-6E13-47F9-80AE-D6AA7699CC35}.Release|Mixed Platforms.Build.0 = Release|Any CPU {28D4DA20-6E13-47F9-80AE-D6AA7699CC35}.Release|x86.ActiveCfg = Release|Any CPU {28D4DA20-6E13-47F9-80AE-D6AA7699CC35}.Release|x86.Build.0 = Release|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Debug|x86.ActiveCfg = Debug|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Debug|x86.Build.0 = Debug|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Release|Any CPU.Build.0 = Release|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Release|x86.ActiveCfg = Release|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Release|x86.Build.0 = Release|Any CPU {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}.Debug|Any CPU.Build.0 = Debug|Any CPU {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -531,18 +515,6 @@ Global {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}.Release|Mixed Platforms.Build.0 = Release|Any CPU {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}.Release|x86.ActiveCfg = Release|Any CPU {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}.Release|x86.Build.0 = Release|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Debug|x86.ActiveCfg = Debug|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Debug|x86.Build.0 = Debug|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Release|Any CPU.Build.0 = Release|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Release|x86.ActiveCfg = Release|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Release|x86.Build.0 = Release|Any CPU {829D9A67-2D07-4CE6-86C0-59F2549B0CFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {829D9A67-2D07-4CE6-86C0-59F2549B0CFA}.Debug|Any CPU.Build.0 = Debug|Any CPU {829D9A67-2D07-4CE6-86C0-59F2549B0CFA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -641,9 +613,7 @@ Global {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {0AB46520-F441-4E01-B444-08F4D23F8B1B} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {28D4DA20-6E13-47F9-80AE-D6AA7699CC35} = {44546170-35BF-448F-88F5-4331AE67AEAE} - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {829D9A67-2D07-4CE6-86C0-59F2549B0CFA} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {0772E545-A674-4165-9469-E3D79D88A4A8} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {92D959F2-66B8-490A-BA33-DA4421EBC948} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} diff --git a/Mvc.sln b/Mvc.sln index 2bcf1d1580..775683a5fa 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -162,10 +162,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.An EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc.Analyzers.Test", "test\Mvc.Analyzers.Test\Mvc.Analyzers.Test.csproj", "{E3E09D2F-1FCF-4396-9B09-5A62CA8CC831}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Analyzers.Experimental", "src\Microsoft.AspNetCore.Mvc.Analyzers.Experimental\Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj", "{CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test", "test\Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test\Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test.csproj", "{E83D3745-9BCF-40E8-8D34-AFBA604C2439}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPagesClassLibrary", "test\WebSites\RazorPagesClassLibrary\RazorPagesClassLibrary.csproj", "{17122147-ADFD-41C8-87D9-CCC582CCA8F9}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Views.TestCommon", "test\Microsoft.AspNetCore.Mvc.Views.TestCommon\Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj", "{51E3E785-A9D1-4196-BAFE-A17FF4304B89}" @@ -858,30 +854,6 @@ Global {E3E09D2F-1FCF-4396-9B09-5A62CA8CC831}.Release|Mixed Platforms.Build.0 = Release|Any CPU {E3E09D2F-1FCF-4396-9B09-5A62CA8CC831}.Release|x86.ActiveCfg = Release|Any CPU {E3E09D2F-1FCF-4396-9B09-5A62CA8CC831}.Release|x86.Build.0 = Release|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Debug|x86.ActiveCfg = Debug|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Debug|x86.Build.0 = Debug|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Release|Any CPU.Build.0 = Release|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Release|x86.ActiveCfg = Release|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Release|x86.Build.0 = Release|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Debug|x86.ActiveCfg = Debug|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Debug|x86.Build.0 = Debug|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Release|Any CPU.Build.0 = Release|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Release|x86.ActiveCfg = Release|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Release|x86.Build.0 = Release|Any CPU {17122147-ADFD-41C8-87D9-CCC582CCA8F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {17122147-ADFD-41C8-87D9-CCC582CCA8F9}.Debug|Any CPU.Build.0 = Debug|Any CPU {17122147-ADFD-41C8-87D9-CCC582CCA8F9}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -1031,8 +1003,6 @@ Global {8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {87A3E227-C45E-4141-A59F-402908E651FD} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {E3E09D2F-1FCF-4396-9B09-5A62CA8CC831} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} - {E83D3745-9BCF-40E8-8D34-AFBA604C2439} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {17122147-ADFD-41C8-87D9-CCC582CCA8F9} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {51E3E785-A9D1-4196-BAFE-A17FF4304B89} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {910F023A-88E3-4CB4-8793-AC4005C7B421} = {2859F266-673A-45A2-9E3C-7B39C6DDD38E} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidAnalyzer.cs deleted file mode 100644 index e3a489a960..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidAnalyzer.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class ActionsMustNotBeAsyncVoidAnalyzer : ControllerAnalyzerBase - { - public static readonly string ReturnTypeKey = "ReturnType"; - - public ActionsMustNotBeAsyncVoidAnalyzer() - : base(ExperimentalDiagnosticDescriptors.MVC7003_ActionsMustNotBeAsyncVoid) - { - } - - protected override void InitializeWorker(ControllerAnalyzerContext analyzerContext) - { - analyzerContext.Context.RegisterSyntaxNodeAction(context => - { - var methodSyntax = (MethodDeclarationSyntax)context.Node; - var method = context.SemanticModel.GetDeclaredSymbol(methodSyntax, context.CancellationToken); - - if (!analyzerContext.IsControllerAction(method)) - { - return; - } - - if (!method.IsAsync || !method.ReturnsVoid) - { - return; - } - - var returnType = analyzerContext.SystemThreadingTask.ToMinimalDisplayString( - context.SemanticModel, - methodSyntax.ReturnType.SpanStart); - - var properties = ImmutableDictionary.Create(StringComparer.Ordinal) - .Add(ReturnTypeKey, returnType); - - var location = methodSyntax.ReturnType.GetLocation(); - context.ReportDiagnostic(Diagnostic.Create( - SupportedDiagnostic, - location, - properties: properties)); - - }, SyntaxKind.MethodDeclaration); - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidFixProvider.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidFixProvider.cs deleted file mode 100644 index 8adf0c0288..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidFixProvider.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Immutable; -using System.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Editing; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - [ExportCodeFixProvider(LanguageNames.CSharp)] - [Shared] - public class ActionsMustNotBeAsyncVoidFixProvider : CodeFixProvider - { - public sealed override ImmutableArray FixableDiagnosticIds => - ImmutableArray.Create(ExperimentalDiagnosticDescriptors.MVC7003_ActionsMustNotBeAsyncVoid.Id); - - public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - if (context.Diagnostics.Length == 0) - { - return; - } - - if (!context.Diagnostics[0].Properties.TryGetValue(ActionsMustNotBeAsyncVoidAnalyzer.ReturnTypeKey, out var returnTypeName)) - { - return; - } - - var rootNode = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - - const string title = "Fix async void usage."; - context.RegisterCodeFix( - CodeAction.Create( - title, - createChangedDocument: CreateChangedDocumentAsync, - equivalenceKey: title), - context.Diagnostics); - - async Task CreateChangedDocumentAsync(CancellationToken cancellationToken) - { - var returnTypeSyntax = rootNode.FindNode(context.Span); - - var editor = await DocumentEditor.CreateAsync(context.Document, cancellationToken).ConfigureAwait(false); - editor.ReplaceNode(returnTypeSyntax, SyntaxFactory.IdentifierName(returnTypeName)); - - return editor.GetChangedDocument(); - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedAnalyzer.cs deleted file mode 100644 index 32b7a4e3ac..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedAnalyzer.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class ApiActionsAreAttributeRoutedAnalyzer : ApiControllerAnalyzerBase - { - internal const string MethodNameKey = "MethodName"; - - public ApiActionsAreAttributeRoutedAnalyzer() - : base(ExperimentalDiagnosticDescriptors.MVC7000_ApiActionsMustBeAttributeRouted) - { - } - - protected override void InitializeWorker(ApiControllerAnalyzerContext analyzerContext) - { - analyzerContext.Context.RegisterSymbolAction(context => - { - var method = (IMethodSymbol)context.Symbol; - - if (!analyzerContext.IsApiAction(method)) - { - return; - } - - foreach (var attribute in method.GetAttributes()) - { - if (attribute.AttributeClass.IsAssignableFrom(analyzerContext.RouteAttribute)) - { - return; - } - } - - var properties = ImmutableDictionary.Create(StringComparer.Ordinal) - .Add(MethodNameKey, method.Name); - - var location = method.Locations.Length > 0 ? method.Locations[0] : Location.None; - context.ReportDiagnostic(Diagnostic.Create( - SupportedDiagnostic, - location, - properties: properties)); - - }, SymbolKind.Method); - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedFixProvider.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedFixProvider.cs deleted file mode 100644 index f89827b8f0..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedFixProvider.cs +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Immutable; -using System.Composition; -using System.Diagnostics; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Editing; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - [ExportCodeFixProvider(LanguageNames.CSharp)] - [Shared] - public class ApiActionsAreAttributeRoutedFixProvider : CodeFixProvider - { - private static readonly RouteAttributeInfo[] RouteAttributes = new[] - { - new RouteAttributeInfo("HttpGet", TypeNames.HttpGetAttribute, new[] { "Get", "Find" }), - new RouteAttributeInfo("HttpPost", TypeNames.HttpPostAttribute, new[] { "Post", "Create", "Update" }), - new RouteAttributeInfo("HttpDelete", TypeNames.HttpDeleteAttribute, new[] { "Delete", "Remove" }), - new RouteAttributeInfo("HttpPut", TypeNames.HttpPutAttribute, new[] { "Put", "Create", "Update" }), - }; - - public sealed override ImmutableArray FixableDiagnosticIds => - ImmutableArray.Create(ExperimentalDiagnosticDescriptors.MVC7000_ApiActionsMustBeAttributeRouted.Id); - - public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - if (context.Diagnostics.Length == 0) - { - return; - } - - var rootNode = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - - Debug.Assert(context.Diagnostics.Length == 1); - var diagnostic = context.Diagnostics[0]; - var methodName = diagnostic.Properties[ApiActionsAreAttributeRoutedAnalyzer.MethodNameKey]; - - var matchedByKeyword = false; - foreach (var routeInfo in RouteAttributes) - { - foreach (var keyword in routeInfo.KeyWords) - { - // Determine if the method starts with a conventional key and only show relevant routes. - // For e.g. FindPetByCategory would result in HttpGet attribute. - if (methodName.StartsWith(keyword, StringComparison.Ordinal)) - { - matchedByKeyword = true; - - var title = $"Add {routeInfo.Name} attribute"; - context.RegisterCodeFix( - CodeAction.Create( - title, - createChangedDocument: cancellationToken => CreateChangedDocumentAsync(routeInfo.Type, cancellationToken), - equivalenceKey: title), - context.Diagnostics); - } - } - } - - if (!matchedByKeyword) - { - foreach (var routeInfo in RouteAttributes) - { - var title = $"Add {routeInfo.Name} attribute"; - context.RegisterCodeFix( - CodeAction.Create( - title, - createChangedDocument: cancellationToken => CreateChangedDocumentAsync(routeInfo.Type, cancellationToken), - equivalenceKey: title), - context.Diagnostics); - } - } - - async Task CreateChangedDocumentAsync(string attributeName, CancellationToken cancellationToken) - { - var methodNode = (MethodDeclarationSyntax)rootNode.FindNode(context.Span); - - var editor = await DocumentEditor.CreateAsync(context.Document, cancellationToken).ConfigureAwait(false); - var compilation = editor.SemanticModel.Compilation; - var attributeMetadata = compilation.GetTypeByMetadataName(attributeName); - var fromRouteAttribute = compilation.GetTypeByMetadataName(TypeNames.FromRouteAttribute); - - attributeName = attributeMetadata.ToMinimalDisplayString(editor.SemanticModel, methodNode.SpanStart); - - // Remove the Attribute suffix from type names e.g. "HttpGetAttribute" -> "HttpGet" - if (attributeName.EndsWith("Attribute", StringComparison.Ordinal)) - { - attributeName = attributeName.Substring(0, attributeName.Length - "Attribute".Length); - } - - var method = editor.SemanticModel.GetDeclaredSymbol(methodNode); - - var attribute = SyntaxFactory.Attribute( - SyntaxFactory.ParseName(attributeName)); - - var route = GetRoute(fromRouteAttribute, method); - if (!string.IsNullOrEmpty(route)) - { - attribute = attribute.AddArgumentListArguments( - SyntaxFactory.AttributeArgument( - SyntaxFactory.LiteralExpression( - SyntaxKind.StringLiteralExpression, - SyntaxFactory.Literal(route)))); - } - - editor.AddAttribute(methodNode, attribute); - return editor.GetChangedDocument(); - } - } - - private static string GetRoute(ITypeSymbol fromRouteAttribute, IMethodSymbol method) - { - StringBuilder routeNameBuilder = null; - - foreach (var parameter in method.Parameters) - { - if (IsIdParameter(parameter.Name) || parameter.HasAttribute(fromRouteAttribute)) - { - if (routeNameBuilder == null) - { - routeNameBuilder = new StringBuilder(parameter.Name.Length + 2); - } - else - { - routeNameBuilder.Append("/"); - } - - routeNameBuilder - .Append("{") - .Append(parameter.Name) - .Append("}"); - } - } - - return routeNameBuilder?.ToString(); - } - - private static bool IsIdParameter(string name) - { - // Check if the parameter is named "id" (e.g. int id) or ends in Id (e.g. personId) - if (name == null || name.Length < 2) - { - return false; - } - - if (string.Equals("id", name, StringComparison.Ordinal)) - { - return true; - } - - if (name.Length > 3 && name.EndsWith("Id", StringComparison.Ordinal) && char.IsLower(name[name.Length - 3])) - { - return true; - } - - return false; - } - - private readonly struct RouteAttributeInfo - { - public RouteAttributeInfo(string name, string type, string[] keywords) - { - Name = name; - Type = type; - KeyWords = keywords; - } - - public string Name { get; } - - public string Type { get; } - - public string[] KeyWords { get; } - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTAnalyzer.cs deleted file mode 100644 index 9125d40692..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTAnalyzer.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class ApiActionsShouldUseActionResultOfTAnalyzer : ApiControllerAnalyzerBase - { - public static readonly string ReturnTypeKey = "ReturnType"; - - public ApiActionsShouldUseActionResultOfTAnalyzer() - : base(ExperimentalDiagnosticDescriptors.MVC7002_ApiActionsShouldReturnActionResultOf) - { - } - - protected override void InitializeWorker(ApiControllerAnalyzerContext analyzerContext) - { - analyzerContext.Context.RegisterSyntaxNodeAction(context => - { - var methodSyntax = (MethodDeclarationSyntax)context.Node; - if (methodSyntax.Body == null) - { - // Ignore expression bodied methods. - } - - var method = context.SemanticModel.GetDeclaredSymbol(methodSyntax, context.CancellationToken); - if (!analyzerContext.IsApiAction(method)) - { - return; - } - - if (method.ReturnsVoid || method.ReturnType.Kind != SymbolKind.NamedType) - { - return; - } - - var declaredReturnType = method.ReturnType; - var namedReturnType = (INamedTypeSymbol)method.ReturnType; - var isTaskOActionResult = false; - if (namedReturnType.ConstructedFrom?.IsAssignableFrom(analyzerContext.SystemThreadingTaskOfT) ?? false) - { - // Unwrap Task. - isTaskOActionResult = true; - declaredReturnType = namedReturnType.TypeArguments[0]; - } - - if (!declaredReturnType.IsAssignableFrom(analyzerContext.IActionResult)) - { - // Method signature does not look like IActionResult MyAction or SomeAwaitable. - // Nothing to do here. - return; - } - - // Method returns an IActionResult. Determine if the method block returns an ObjectResult - foreach (var returnStatement in methodSyntax.DescendantNodes().OfType()) - { - var returnType = context.SemanticModel.GetTypeInfo(returnStatement.Expression, context.CancellationToken); - if (returnType.Type == null || returnType.Type.Kind == SymbolKind.ErrorType) - { - continue; - } - - ImmutableDictionary properties = null; - if (returnType.Type.IsAssignableFrom(analyzerContext.ObjectResult)) - { - // Check if the method signature looks like "return Ok(userModelInstance)". If so, we can infer the type of userModelInstance - if (returnStatement.Expression is InvocationExpressionSyntax invocation && - invocation.ArgumentList.Arguments.Count == 1) - { - var typeInfo = context.SemanticModel.GetTypeInfo(invocation.ArgumentList.Arguments[0].Expression); - var desiredReturnType = analyzerContext.ActionResultOfT.Construct(typeInfo.Type); - if (isTaskOActionResult) - { - desiredReturnType = analyzerContext.SystemThreadingTaskOfT.Construct(desiredReturnType); - } - - var desiredReturnTypeString = desiredReturnType.ToMinimalDisplayString( - context.SemanticModel, - methodSyntax.ReturnType.SpanStart); - - properties = ImmutableDictionary.Create(StringComparer.Ordinal) - .Add(ReturnTypeKey, desiredReturnTypeString); - } - - context.ReportDiagnostic(Diagnostic.Create( - SupportedDiagnostic, - methodSyntax.ReturnType.GetLocation(), - properties: properties)); - } - } - }, SyntaxKind.MethodDeclaration); - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTCodeFixProvider.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTCodeFixProvider.cs deleted file mode 100644 index 7dc5b5ec14..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTCodeFixProvider.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Immutable; -using System.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Editing; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - [ExportCodeFixProvider(LanguageNames.CSharp)] - [Shared] - public class ApiActionsShouldUseActionResultOfTCodeFixProvider : CodeFixProvider - { - public sealed override ImmutableArray FixableDiagnosticIds => - ImmutableArray.Create(ExperimentalDiagnosticDescriptors.MVC7002_ApiActionsShouldReturnActionResultOf.Id); - - public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - var rootNode = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - - foreach (var diagnostic in context.Diagnostics) - { - if (diagnostic.Properties.TryGetValue("ReturnType", out var returnTypeName)) - { - - var title = $"Make return type {returnTypeName}"; - context.RegisterCodeFix( - CodeAction.Create( - title, - createChangedDocument: cancellationToken => CreateChangedDocumentAsync(returnTypeName, cancellationToken), - equivalenceKey: title), - context.Diagnostics); - } - } - - async Task CreateChangedDocumentAsync(string returnTypeName, CancellationToken cancellationToken) - { - var returnType = rootNode.FindNode(context.Span); - - var editor = await DocumentEditor.CreateAsync(context.Document, cancellationToken).ConfigureAwait(false); - editor.ReplaceNode(returnType, SyntaxFactory.IdentifierName(returnTypeName)); - - return editor.GetChangedDocument(); - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerBase.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerBase.cs deleted file mode 100644 index b70d1409de..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerBase.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - public abstract class ApiControllerAnalyzerBase : DiagnosticAnalyzer - { - public ApiControllerAnalyzerBase(DiagnosticDescriptor diagnostic) - { - SupportedDiagnostics = ImmutableArray.Create(diagnostic); - } - - protected DiagnosticDescriptor SupportedDiagnostic => SupportedDiagnostics[0]; - - public override ImmutableArray SupportedDiagnostics { get; } - - public sealed override void Initialize(AnalysisContext context) - { - context.RegisterCompilationStartAction(compilationContext => - { - var analyzerContext = new ApiControllerAnalyzerContext(compilationContext); - - // Only do work if ApiControllerAttribute is defined. - if (analyzerContext.ApiControllerAttribute == null) - { - return; - } - - InitializeWorker(analyzerContext); - }); - } - - protected abstract void InitializeWorker(ApiControllerAnalyzerContext analyzerContext); - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerContext.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerContext.cs deleted file mode 100644 index 1ab36e3dac..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerContext.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - public class ApiControllerAnalyzerContext - { -#pragma warning disable RS1012 // Start action has no registered actions. - public ApiControllerAnalyzerContext(CompilationStartAnalysisContext context) -#pragma warning restore RS1012 // Start action has no registered actions. - { - Context = context; - ApiControllerAttribute = context.Compilation.GetTypeByMetadataName(TypeNames.ApiControllerAttribute); - } - - public CompilationStartAnalysisContext Context { get; } - - public INamedTypeSymbol ApiControllerAttribute { get; } - - private INamedTypeSymbol _routeAttribute; - public INamedTypeSymbol RouteAttribute => GetType(TypeNames.IRouteTemplateProvider, ref _routeAttribute); - - private INamedTypeSymbol _actionResultOfT; - public INamedTypeSymbol ActionResultOfT => GetType(TypeNames.ActionResultOfT, ref _actionResultOfT); - - private INamedTypeSymbol _systemThreadingTask; - public INamedTypeSymbol SystemThreadingTask => GetType(TypeNames.Task, ref _systemThreadingTask); - - private INamedTypeSymbol _systemThreadingTaskOfT; - public INamedTypeSymbol SystemThreadingTaskOfT => GetType(TypeNames.TaskOfT, ref _systemThreadingTaskOfT); - - private INamedTypeSymbol _objectResult; - public INamedTypeSymbol ObjectResult => GetType(TypeNames.ObjectResult, ref _objectResult); - - private INamedTypeSymbol _iActionResult; - public INamedTypeSymbol IActionResult => GetType(TypeNames.IActionResult, ref _iActionResult); - - public INamedTypeSymbol _modelState; - public INamedTypeSymbol ModelStateDictionary => GetType(TypeNames.ModelStateDictionary, ref _modelState); - - public INamedTypeSymbol _nonActionAttribute; - public INamedTypeSymbol NonActionAttribute => GetType(TypeNames.NonActionAttribute, ref _nonActionAttribute); - - - private INamedTypeSymbol GetType(string name, ref INamedTypeSymbol cache) => - cache = cache ?? Context.Compilation.GetTypeByMetadataName(name); - - public bool IsApiAction(IMethodSymbol method) - { - return - method.ContainingType.HasAttribute(ApiControllerAttribute, inherit: true) && - method.DeclaredAccessibility == Accessibility.Public && - method.MethodKind == MethodKind.Ordinary && - !method.IsGenericMethod && - !method.IsAbstract && - !method.IsStatic && - !method.HasAttribute(NonActionAttribute); - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/CodeAnalysisExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/CodeAnalysisExtensions.cs deleted file mode 100644 index a8ae5b8a0f..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/CodeAnalysisExtensions.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Diagnostics; -using Microsoft.CodeAnalysis; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - internal static class CodeAnalysisExtensions - { - public static bool HasAttribute(this ITypeSymbol typeSymbol, ITypeSymbol attribute, bool inherit) - { - while (typeSymbol != null) - { - if (typeSymbol.HasAttribute(attribute)) - { - return true; - } - - typeSymbol = typeSymbol.BaseType; - } - - return false; - } - - public static bool HasAttribute(this ISymbol symbol, ITypeSymbol attribute) - { - Debug.Assert(symbol != null); - Debug.Assert(attribute != null); - - foreach (var declaredAttribute in symbol.GetAttributes()) - { - if (declaredAttribute.AttributeClass == attribute) - { - return true; - } - } - - return false; - } - - public static bool IsAssignableFrom(this ITypeSymbol source, INamedTypeSymbol target) - { - Debug.Assert(source != null); - Debug.Assert(target != null); - - if (source == target) - { - return true; - } - - if (target.TypeKind == TypeKind.Interface) - { - foreach (var @interface in source.AllInterfaces) - { - if (@interface == target) - { - return true; - } - } - - return false; - } - - do - { - if (source == target) - { - return true; - } - - source = source.BaseType; - } while (source != null); - - return false; - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerBase.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerBase.cs deleted file mode 100644 index 71c497fa29..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerBase.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - public abstract class ControllerAnalyzerBase : DiagnosticAnalyzer - { - public ControllerAnalyzerBase(DiagnosticDescriptor diagnostic) - { - SupportedDiagnostics = ImmutableArray.Create(diagnostic); - } - - protected DiagnosticDescriptor SupportedDiagnostic => SupportedDiagnostics[0]; - - public override ImmutableArray SupportedDiagnostics { get; } - - public sealed override void Initialize(AnalysisContext context) - { - context.RegisterCompilationStartAction(compilationContext => - { - var analyzerContext = new ControllerAnalyzerContext(compilationContext); - - // Only do work if ControllerAttribute is defined. - if (analyzerContext.ControllerAttribute == null) - { - return; - } - - InitializeWorker(analyzerContext); - }); - } - - protected abstract void InitializeWorker(ControllerAnalyzerContext analyzerContext); - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerContext.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerContext.cs deleted file mode 100644 index 8a9d403a8a..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerContext.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - public class ControllerAnalyzerContext - { -#pragma warning disable RS1012 // Start action has no registered actions. - public ControllerAnalyzerContext(CompilationStartAnalysisContext context) -#pragma warning restore RS1012 // Start action has no registered actions. - { - Context = context; - ControllerAttribute = Context.Compilation.GetTypeByMetadataName(TypeNames.ControllerAttribute); - } - - public CompilationStartAnalysisContext Context { get; } - - public INamedTypeSymbol ControllerAttribute { get; } - - private INamedTypeSymbol _systemThreadingTask; - public INamedTypeSymbol SystemThreadingTask => GetType(TypeNames.Task, ref _systemThreadingTask); - - private INamedTypeSymbol _systemThreadingTaskOfT; - public INamedTypeSymbol SystemThreadingTaskOfT => GetType(TypeNames.TaskOfT, ref _systemThreadingTaskOfT); - - public INamedTypeSymbol _nonActionAttribute; - public INamedTypeSymbol NonActionAttribute => GetType(TypeNames.NonActionAttribute, ref _nonActionAttribute); - - private INamedTypeSymbol GetType(string name, ref INamedTypeSymbol cache) => - cache = cache ?? Context.Compilation.GetTypeByMetadataName(name); - - public bool IsControllerAction(IMethodSymbol method) - { - return - method.ContainingType.HasAttribute(ControllerAttribute, inherit: true) && - method.DeclaredAccessibility == Accessibility.Public && - method.MethodKind == MethodKind.Ordinary && - !method.IsGenericMethod && - !method.IsAbstract && - !method.IsStatic && - !method.HasAttribute(NonActionAttribute); - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/DiagnosticDescriptors.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/DiagnosticDescriptors.cs deleted file mode 100644 index 14f56b5918..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/DiagnosticDescriptors.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.CodeAnalysis; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - public static class ExperimentalDiagnosticDescriptors - { - public static readonly DiagnosticDescriptor MVC7000_ApiActionsMustBeAttributeRouted = - new DiagnosticDescriptor( - "MVC7000", - "Actions on types annotated with ApiControllerAttribute must be attribute routed.", - "Actions on types annotated with ApiControllerAttribute must be attribute routed.", - "Usage", - DiagnosticSeverity.Warning, - isEnabledByDefault: true); - - public static readonly DiagnosticDescriptor MVC7001_ApiActionsHaveBadModelStateFilter = - new DiagnosticDescriptor( - "MVC7001", - "Actions on types annotated with ApiControllerAttribute do not require explicit ModelState validity check.", - "Actions on types annotated with ApiControllerAttribute do not require explicit ModelState validity check.", - "Usage", - DiagnosticSeverity.Warning, - isEnabledByDefault: true); - - public static readonly DiagnosticDescriptor MVC7002_ApiActionsShouldReturnActionResultOf = - new DiagnosticDescriptor( - "MVC7002", - "Actions on types annotated with ApiControllerAttribute should return ActionResult.", - "Actions on types annotated with ApiControllerAttribute should return ActionResult.", - "Usage", - DiagnosticSeverity.Warning, - isEnabledByDefault: true); - - public static readonly DiagnosticDescriptor MVC7003_ActionsMustNotBeAsyncVoid = - new DiagnosticDescriptor( - "MVC7003", - "Controller actions must not have async void signature.", - "Controller actions must not have async void signature.", - "Usage", - DiagnosticSeverity.Warning, - isEnabledByDefault: true); - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj deleted file mode 100644 index 9c4237c2ab..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - CSharp Analyzers for ASP.NET Core MVC. - aspnetcore;aspnetcoremvc - - false - $(ExperimentalVersionPrefix) - $(ExperimentalVersionSuffix) - $(ExperimentalPackageVersion) - - netstandard1.3 - false - false - false - - - - - - - - - - - - - - - diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/TypeNames.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/TypeNames.cs deleted file mode 100644 index 810c63a7bd..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/TypeNames.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - internal static class TypeNames - { - public const string ControllerAttribute = "Microsoft.AspNetCore.Mvc.ControllerAttribute"; - - public const string ApiControllerAttribute = "Microsoft.AspNetCore.Mvc.ApiControllerAttribute"; - - public const string NonActionAttribute = "Microsoft.AspNetCore.Mvc.NonActionAttribute"; - - public const string IRouteTemplateProvider = "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider"; - - public const string ActionResultOfT = "Microsoft.AspNetCore.Mvc.ActionResult`1"; - - public const string Task = "System.Threading.Tasks.Task"; - - public const string TaskOfT = "System.Threading.Tasks.Task`1"; - - public const string ObjectResult = "Microsoft.AspNetCore.Mvc.ObjectResult"; - - public const string IActionResult = "Microsoft.AspNetCore.Mvc.IActionResult"; - - public const string ModelStateDictionary = "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary"; - - public const string HttpGetAttribute = "Microsoft.AspNetCore.Mvc.HttpGetAttribute"; - - public const string HttpPostAttribute = "Microsoft.AspNetCore.Mvc.HttpPostAttribute"; - - public const string HttpPutAttribute = "Microsoft.AspNetCore.Mvc.HttpPutAttribute"; - - public const string HttpDeleteAttribute = "Microsoft.AspNetCore.Mvc.HttpDeleteAttribute"; - - public const string FromRouteAttribute = "Microsoft.AspNetCore.Mvc.FromRouteAttribute"; - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ActionsMustNotBeAsyncVoidFacts.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ActionsMustNotBeAsyncVoidFacts.cs deleted file mode 100644 index d3fafb9f5c..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ActionsMustNotBeAsyncVoidFacts.cs +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - public class ActionsMustNotBeAsyncVoidFacts : AnalyzerTestBase - { - private static DiagnosticDescriptor DiagnosticDescriptor = ExperimentalDiagnosticDescriptors.MVC7003_ActionsMustNotBeAsyncVoid; - - protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } - = new ActionsMustNotBeAsyncVoidAnalyzer(); - - protected override CodeFixProvider CodeFixProvider { get; } - = new ActionsMustNotBeAsyncVoidFixProvider(); - - [Fact] - public async Task NoDiagnosticsAreReturned_FoEmptyScenarios() - { - // Arrange - var test = @""; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_WhenMethodIsNotAControllerAction() - { - // Arrange - var test = -@" -using System.Threading.Tasks; - -public class UserViewModel -{ - public async void Index() => await Task.Delay(10); -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task DiagnosticsAreReturned_WhenMethodIsAControllerAction() - { - // Arrange - var location = new DiagnosticLocation("Test.cs", 7, 18); - var test = -@" -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; - -public class HomeController : Controller -{ - public async void Index() - { - await Response.Body.FlushAsync(); - } -}"; - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; - -public class HomeController : Controller -{ - public async Task Index() - { - await Response.Body.FlushAsync(); - } -}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - AssertDiagnostic(location, actualDiagnostics); - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - [Fact] - public async Task DiagnosticsAreReturned_WhenActionMethodIsExpressionBodied() - { - // Arrange - var location = new DiagnosticLocation("Test.cs", 7, 18); - var test = -@" -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; - -public class HomeController : Controller -{ - public async void Index() => await Response.Body.FlushAsync(); -}"; - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; - -public class HomeController : Controller -{ - public async Task Index() => await Response.Body.FlushAsync(); -}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - AssertDiagnostic(location, actualDiagnostics); - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - [Fact] - public async Task CodeFix_ProducesFullyQualifiedNamespaces() - { - // Arrange - var location = new DiagnosticLocation("Test.cs", 6, 18); - var test = -@" -using Microsoft.AspNetCore.Mvc; - -public class HomeController : Controller -{ - public async void Index() => await Response.Body.FlushAsync(); -}"; - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; - -public class HomeController : Controller -{ - public async System.Threading.Tasks.Task Index() => await Response.Body.FlushAsync(); -}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - AssertDiagnostic(location, actualDiagnostics); - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - private void AssertDiagnostic(DiagnosticLocation expectedLocation, Diagnostic[] actualDiagnostics) - { - // Assert - Assert.Collection( - actualDiagnostics, - diagnostic => - { - Assert.Equal(DiagnosticDescriptor.Id, diagnostic.Id); - Assert.Same(DiagnosticDescriptor, diagnostic.Descriptor); - AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); - }); - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsAreAttributeRoutedFacts.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsAreAttributeRoutedFacts.cs deleted file mode 100644 index d7968d7928..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsAreAttributeRoutedFacts.cs +++ /dev/null @@ -1,304 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading.Tasks; -using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - public class ApiActionsAreAttributeRoutedFacts : AnalyzerTestBase - { - private static DiagnosticDescriptor DiagnosticDescriptor = ExperimentalDiagnosticDescriptors.MVC7000_ApiActionsMustBeAttributeRouted; - - protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } - = new ApiActionsAreAttributeRoutedAnalyzer(); - - protected override CodeFixProvider CodeFixProvider { get; } - = new ApiActionsAreAttributeRoutedFixProvider(); - - [Fact] - public async Task NoDiagnosticsAreReturned_FoEmptyScenarios() - { - // Arrange - var test = @""; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_WhenTypeIsNotApiController() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -public class HomeController : Controller -{ - public IActionResult Index() => View(); -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_WhenApiControllerActionHasAttribute() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController : Controller -{ - [HttpGet] - public int GetPetId() => 0; -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_ForConstructors() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController : Controller -{ - public PetController(){ } -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_ForNonActions() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController : Controller -{ - private int GetPetIdPrivate() => 0; - protected int GetPetIdProtected() => 0; - public static IActionResult FindPetByStatus(int status) => null; - [NonAction] - public object Reset(int state) => null; -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task DiagnosticsAndCodeFixes_WhenApiControllerActionDoesNotHaveAttribute() - { - // Arrange - var expectedLocation = new DiagnosticLocation("Test.cs", 8, 16); - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -[Route] -public class PetController : Controller -{ - public int GetPetId() => 0; -}"; - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -[Route] -public class PetController : Controller -{ - [HttpGet] - public int GetPetId() => 0; -}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - AssertDiagnostic(expectedLocation, actualDiagnostics); - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - [Fact] - public async Task CodeFixes_ApplyFullyQualifiedNames() - { - // Arrange - var test = -@" -[Microsoft.AspNetCore.Mvc.ApiController] -[Microsoft.AspNetCore.Mvc.Route] -public class PetController -{ - public object GetPet() => null; -}"; - var expectedFix = -@" -[Microsoft.AspNetCore.Mvc.ApiController] -[Microsoft.AspNetCore.Mvc.Route] -public class PetController -{ - [Microsoft.AspNetCore.Mvc.HttpGet] - public object GetPet() => null; -}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - [Theory] - [InlineData("id")] - [InlineData("petId")] - public async Task CodeFixes_WithIdParameter(string idParameter) - { - // Arrange - var test = -$@" -using Microsoft.AspNetCore.Mvc; -[ApiController] -[Route] -public class PetController -{{ - public IActionResult Post(string notid, int {idParameter}) => null; -}}"; - var expectedFix = -$@" -using Microsoft.AspNetCore.Mvc; -[ApiController] -[Route] -public class PetController -{{ - [HttpPost(""{{{idParameter}}}"")] - public IActionResult Post(string notid, int {idParameter}) => null; -}}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - [Fact] - public async Task CodeFixes_WithRouteParameter() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; -[ApiController] -[Route] -public class PetController -{ - public IActionResult DeletePetByStatus([FromRoute] Status status, [FromRoute] Category category) => null; -}"; - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; -[ApiController] -[Route] -public class PetController -{ - [HttpDelete(""{status}/{category}"")] - public IActionResult DeletePetByStatus([FromRoute] Status status, [FromRoute] Category category) => null; -}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - [Fact] - public async Task CodeFixes_WhenAttributeCannotBeInferred() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; -[ApiController] -[Route] -public class PetController -{ - public IActionResult ModifyPet() => null; -}"; - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; -[ApiController] -[Route] -public class PetController -{ - [HttpPut] - public IActionResult ModifyPet() => null; -}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - // There isn't a good way to test all fixes simultaneously. We'll pick the last one to verify when we - // expect to have 4 fixes. - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics, codeFixIndex: 3); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - private void AssertDiagnostic(DiagnosticLocation expectedLocation, Diagnostic[] actualDiagnostics) - { - // Assert - Assert.Collection( - actualDiagnostics, - diagnostic => - { - Assert.Equal(DiagnosticDescriptor.Id, diagnostic.Id); - Assert.Same(DiagnosticDescriptor, diagnostic.Descriptor); - AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); - }); - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsShouldUseActionResultOfTFacts.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsShouldUseActionResultOfTFacts.cs deleted file mode 100644 index 1cdd409ddc..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsShouldUseActionResultOfTFacts.cs +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading.Tasks; -using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - public class ApiActionsShouldUseActionResultOfTFacts : AnalyzerTestBase - { - private static DiagnosticDescriptor DiagnosticDescriptor = ExperimentalDiagnosticDescriptors.MVC7002_ApiActionsShouldReturnActionResultOf; - - protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } - = new ApiActionsShouldUseActionResultOfTAnalyzer(); - - protected override CodeFixProvider CodeFixProvider { get; } - = new ApiActionsShouldUseActionResultOfTCodeFixProvider(); - - [Fact] - public async Task NoDiagnosticsAreReturned_FoEmptyScenarios() - { - // Arrange - var test = @""; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_WhenTypeIsNotApiController() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -public class HomeController: ControllerBase -{ - public IActionResult Index() => View(); -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_ForNonActions() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController: ControllerBaseBase -{ - private int GetPetIdPrivate() => 0; - protected int GetPetIdProtected() => 0; - public static IActionResult FindPetByStatus(int status) => null; - [NonAction] - public object Reset(int state) => null; -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_WhenActionAreExpressionBodiedMembers() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController: ControllerBase -{ - public IActionResult GetPetId() => ModelState.IsValid ? OK(new object()) : BadResult(); -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Theory] - [InlineData("Pet")] - [InlineData("List")] - [InlineData("System.Threading.Task")] - public async Task NoDiagnosticsAreReturned_WhenTypeReturnsNonObjectResult(string returnType) - { - // Arrange - var test = -$@" -using Microsoft.AspNetCore.Mvc; - -public class Pet {{ }} - -[ApiController] -public class PetController: ControllerBase -{{ - public {returnType} GetPetId() => null; -}}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_WhenTypeReturnsActionResultOfT() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -public class Pet { } - -[ApiController] -public class PetController: ControllerBase -{ - public ActionResult GetPetId() => null; -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task DiagnosticsAreReturned_WhenActionsReturnIActionResult() - { - // Arrange - var expectedLocation = new DiagnosticLocation("Test.cs", 9, 12); - var test = -@" -using Microsoft.AspNetCore.Mvc; - -public class Pet {} - -[ApiController] -public class PetController: ControllerBase -{ - public IActionResult GetPet() - { - return Ok(new Pet()); - } -}"; - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; - -public class Pet {} - -[ApiController] -public class PetController: ControllerBase -{ - public ActionResult GetPet() - { - return Ok(new Pet()); - } -}"; - var project = CreateProject(test); - - // Act - var actualDiagnostics = await GetDiagnosticAsync(project); - AssertDiagnostic(expectedLocation, actualDiagnostics); - - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - [Fact] - public async Task DiagnosticsAreReturned_WhenActionReturnsAsyncIActionResult() - { - // Arrange - var expectedLocation = new DiagnosticLocation("Test.cs", 8, 18); - - var test = -@" -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; - -[ApiController] -public class PetController: ControllerBase -{ - public async Task GetPet() - { - await Task.Delay(0); - return Ok(new Pet()); - } -} -public class Pet {}"; - - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; - -[ApiController] -public class PetController: ControllerBase -{ - public async Task> GetPet() - { - await Task.Delay(0); - return Ok(new Pet()); - } -} -public class Pet {}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - AssertDiagnostic(expectedLocation, actualDiagnostics); - - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - private void AssertDiagnostic(DiagnosticLocation expectedLocation, Diagnostic[] actualDiagnostics) - { - // Assert - Assert.Collection( - actualDiagnostics, - diagnostic => - { - Assert.Equal(DiagnosticDescriptor.Id, diagnostic.Id); - Assert.Same(DiagnosticDescriptor, diagnostic.Descriptor); - AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); - }); - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Infrastructure/AnalyzerTestBase.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Infrastructure/AnalyzerTestBase.cs deleted file mode 100644 index 1351b24c94..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Infrastructure/AnalyzerTestBase.cs +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Testing; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Text; -using Microsoft.Extensions.DependencyModel; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure -{ - public abstract class AnalyzerTestBase : IDisposable - { - private static readonly object WorkspaceLock = new object(); - - public Workspace Workspace { get; private set; } - - protected abstract DiagnosticAnalyzer DiagnosticAnalyzer { get; } - - protected virtual CodeFixProvider CodeFixProvider { get; } - - public IDictionary MarkerLocations { get; } = new Dictionary(); - - public DiagnosticLocation DefaultMarkerLocation { get; private set; } - - protected Project CreateProjectFromFile([CallerMemberName] string fileName = "") - { - var solutionDirectory = TestPathUtilities.GetSolutionRootDirectory("Mvc"); - var projectDirectory = Path.Combine(solutionDirectory, "test", GetType().Assembly.GetName().Name); - - var filePath = Path.Combine(projectDirectory, "TestFiles", fileName + ".cs"); - if (!File.Exists(filePath)) - { - throw new FileNotFoundException($"TestFile {fileName} could not be found at {filePath}.", filePath); - } - - const string MarkerStart = "/*MM"; - const string MarkerEnd = "*/"; - - var lines = File.ReadAllLines(filePath); - for (var i = 0; i < lines.Length; i++) - { - var line = lines[i]; - var markerStartIndex = line.IndexOf(MarkerStart, StringComparison.Ordinal); - if (markerStartIndex != -1) - { - var markerEndIndex = line.IndexOf(MarkerEnd, markerStartIndex, StringComparison.Ordinal); - var markerName = line.Substring(markerStartIndex + 2, markerEndIndex - markerStartIndex - 2); - var resultLocation = new DiagnosticLocation(i + 1, markerStartIndex + 1); ; - - if (DefaultMarkerLocation == null) - { - DefaultMarkerLocation = resultLocation; - } - - MarkerLocations[markerName] = resultLocation; - line = line.Substring(0, markerStartIndex) + line.Substring(markerEndIndex + MarkerEnd.Length); - } - - lines[i] = line; - } - - var inputSource = string.Join(Environment.NewLine, lines); - return CreateProject(inputSource); - } - - protected Project CreateProject(string source) - { - var projectId = ProjectId.CreateNewId(debugName: "TestProject"); - var newFileName = "Test.cs"; - var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); - var metadataReferences = DependencyContext.Load(GetType().Assembly) - .CompileLibraries - .SelectMany(c => c.ResolveReferencePaths()) - .Select(path => MetadataReference.CreateFromFile(path)) - .Cast() - .ToList(); - - lock (WorkspaceLock) - { - if (Workspace == null) - { - Workspace = new AdhocWorkspace(); - } - } - - var solution = Workspace - .CurrentSolution - .AddProject(projectId, "TestProject", "TestProject", LanguageNames.CSharp) - .AddMetadataReferences(projectId, metadataReferences) - .AddDocument(documentId, newFileName, SourceText.From(source)); - - return solution.GetProject(projectId); - } - - protected async Task GetDiagnosticAsync(Project project) - { - var compilation = await project.GetCompilationAsync(); - var compilationWithAnalyzers = compilation.WithAnalyzers(ImmutableArray.Create(DiagnosticAnalyzer)); - var diagnostics = await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync(); - return diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); - } - - protected Task ApplyCodeFixAsync( - Project project, - Diagnostic[] analyzerDiagnostic, - int codeFixIndex = 0) - { - var diagnostic = analyzerDiagnostic.Single(); - return ApplyCodeFixAsync(project, diagnostic, codeFixIndex); - } - - protected async Task ApplyCodeFixAsync( - Project project, - Diagnostic analyzerDiagnostic, - int codeFixIndex = 0) - { - if (CodeFixProvider == null) - { - throw new InvalidOperationException($"{nameof(CodeFixProvider)} has not been assigned."); - } - - var document = project.Documents.Single(); - var actions = new List(); - var context = new CodeFixContext(document, analyzerDiagnostic, (a, d) => actions.Add(a), CancellationToken.None); - await CodeFixProvider.RegisterCodeFixesAsync(context); - - if (actions.Count == 0) - { - throw new InvalidOperationException("CodeFix produced no actions to apply."); - } - - var updatedSolution = await ApplyFixAsync(actions[codeFixIndex]); - // Todo: figure out why this doesn't work. - // var updatedProject = updatedSolution.GetProject(project.Id); - // await EnsureCompilable(updatedProject); - - var updatedDocument = updatedSolution.GetDocument(document.Id); - var sourceText = await updatedDocument.GetTextAsync(); - return sourceText.ToString(); - } - - private static async Task EnsureCompilable(Project project) - { - var compilation = await project - .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) - .GetCompilationAsync(); - var diagnostics = compilation.GetDiagnostics(); - if (diagnostics.Length != 0) - { - var message = string.Join( - Environment.NewLine, - diagnostics.Select(d => CSharpDiagnosticFormatter.Instance.Format(d))); - throw new InvalidOperationException($"Compilation failed:{Environment.NewLine}{message}"); - } - } - - private static async Task ApplyFixAsync(CodeAction codeAction) - { - var operations = await codeAction.GetOperationsAsync(CancellationToken.None); - return Assert.Single(operations.OfType()).ChangedSolution; - } - - public void Dispose() - { - Workspace?.Dispose(); - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test.csproj deleted file mode 100644 index 86f4228e01..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - $(StandardTestTfms) - true - - - - - - - - - - - - - - - diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/xunit.runner.json b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/xunit.runner.json deleted file mode 100644 index 1c72a421ad..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/xunit.runner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "shadowCopy": false -} diff --git a/version.props b/version.props index e01d0a1143..7bf6e5246c 100644 --- a/version.props +++ b/version.props @@ -9,13 +9,5 @@ $(VersionPrefix)-$(VersionSuffix)-final $(FeatureBranchVersionPrefix)$(VersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-')) $(VersionSuffix)-$(BuildNumber) - - 0.2.0 - preview3 - - $(ExperimentalVersionPrefix) - $(ExperimentalVersionPrefix)-$(ExperimentalVersionSuffix)-final - $(FeatureBranchVersionPrefix)$(ExperimentalVersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-')) - $(ExperimentalVersionSuffix)-$(BuildNumber) From 87c1389b5ae4709ce82b0d1a8dd2d0ca3bfeed65 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 9 Sep 2018 12:23:38 -0700 Subject: [PATCH 4/5] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 150 +++++++++++++++++++-------------------- korebuild-lock.txt | 4 +- 2 files changed, 77 insertions(+), 77 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 4de4f3a0fd..fd1818d1af 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.43.0 2.1.1.1 2.1.1 - 2.2.0-preview2-35143 - 2.2.0-preview1-20180821.1 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-a-preview3-link-generator-16951 - 2.2.0-a-preview3-link-generator-16951 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 + 2.2.0-preview3-35202 + 2.2.0-preview1-20180907.8 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 5.2.6 2.8.0 2.8.0 - 2.2.0-preview2-35143 + 2.2.0-preview3-35202 1.7.0 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 2.1.0 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 2.0.9 - 2.1.2 - 2.2.0-preview1-26618-02 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 + 2.1.3 + 2.2.0-preview2-26905-02 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 15.6.1 4.7.49 2.0.3 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index ad704918df..312f82f9a5 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-20180821.1 -commithash:c8d0cc52cd1abb697be24e288ffd54f8fae8bf17 +version:2.2.0-preview1-20180907.8 +commithash:078918eb5c1f176ee1da351c584fb4a4d7491aa0 From f573b8840a0473ff95cd69894ea1f0a6cf1daaa9 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 6 Sep 2018 17:05:11 -0700 Subject: [PATCH 5/5] Fix aspnet/Routing#782 Currently MVC is still running the IActionConstraint implementations for features that we've already moved into the routing layer. This has a significant perf cost associated with, and so we want to skip it because it's redundant. However if anyone has implemented their own `IActionConstraint`-based features, they still need to just work. This change takes the approach of skipping the action constraint phase at runtime unless we see something 'unknown'. This is an all or nothing choice, and will run action constraints if **any** action constraint we don't special case exists. This is the most compatible behavior (running redundant constraints) when the application is using constraints that the developer implemented. Another approach I considered was to eliminate these constraints as part of the process of building ADs. I don't think that's ideal because people have written code that introspects action constraints. We should consider something like this in 3.0. --- .../Internal/ActionConstraintCache.cs | 31 +++++++- .../Routing/ActionConstraintMatcherPolicy.cs | 6 +- .../CorsHttpMethodActionConstraint.cs | 1 + .../ActionConstraintMatcherPolicyTest.cs | 75 ++++++++++++++++++- 4 files changed, 108 insertions(+), 5 deletions(-) rename test/{Microsoft.AspNetCore.Mvc.Core.Test => Microsoft.AspNetCore.Mvc.Test}/Routing/ActionConstraintMatcherPolicyTest.cs (86%) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs index ff50a463b0..e8bf938aef 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs @@ -192,14 +192,43 @@ namespace Microsoft.AspNetCore.Mvc.Internal for (var i = 0; i < _actions.Items.Count; i++) { var action = _actions.Items[i]; - if (action.ActionConstraints?.Count > 0) + if (action.ActionConstraints?.Count > 0 && HasSignificantActionConstraint(action)) { + // We need to check for some specific action constraint implementations. + // We've implemented consumes, and HTTP method support inside endpoint routing, so + // we don't need to run an 'action constraint phase' if those are the only constraints. found = true; break; } } _hasActionConstraints = found; + + bool HasSignificantActionConstraint(ActionDescriptor action) + { + for (var i = 0; i < action.ActionConstraints.Count; i++) + { + var actionConstraint = action.ActionConstraints[i]; + if (actionConstraint.GetType() == typeof(HttpMethodActionConstraint)) + { + // This one is OK, we implement this in endpoint routing. + } + else if (actionConstraint.GetType().FullName == "Microsoft.AspNetCore.Mvc.Cors.Internal.CorsHttpMethodActionConstraint") + { + // This one is OK, we implement this in endpoint routing. + } + else if (actionConstraint.GetType() == typeof(ConsumesAttribute)) + { + // This one is OK, we implement this in endpoint routing. + } + else + { + return true; + } + } + + return false; + } } return _hasActionConstraints.Value; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs index f858f4fe1e..e03640b59c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs @@ -35,6 +35,9 @@ namespace Microsoft.AspNetCore.Mvc.Routing // Run really late. public override int Order => 100000; + // Internal for testing + internal bool ShouldRunActionConstraints => _actionConstraintCache.CurrentCache.HasActionConstraints; + public Task ApplyAsync(HttpContext httpContext, EndpointFeature endpointFeature, CandidateSet candidateSet) { // PERF: we can skip over action constraints if there aren't any app-wide. @@ -42,8 +45,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing // Running action constraints (or just checking for them) in a candidate set // is somewhat expensive compared to other routing operations. This should only // happen if user-code adds action constraints. - var actions = _actionConstraintCache.CurrentCache; - if (actions.HasActionConstraints) + if (ShouldRunActionConstraints) { ApplyActionConstraints(httpContext, candidateSet); } diff --git a/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsHttpMethodActionConstraint.cs b/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsHttpMethodActionConstraint.cs index 71ba2d696c..de3ff06468 100644 --- a/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsHttpMethodActionConstraint.cs +++ b/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsHttpMethodActionConstraint.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc.Internal; namespace Microsoft.AspNetCore.Mvc.Cors.Internal { + // Don't casually change the name of this. We reference the full type name in ActionConstraintCache. public class CorsHttpMethodActionConstraint : HttpMethodActionConstraint { private readonly string OriginHeader = "Origin"; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs similarity index 86% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs rename to test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs index 5936d34ef6..9889b12e1b 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs @@ -7,16 +7,17 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Mvc.Cors.Internal; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Matching; -using Microsoft.AspNetCore.Routing.Patterns; using Moq; using Xunit; namespace Microsoft.AspNetCore.Mvc.Routing { + // These tests are intentionally in Mvc.Test so we can also test the CORS action constraint. public class ActionConstraintMatcherPolicyTest { [Fact] @@ -49,7 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing { ActionConstraints = new List() { - new HttpMethodActionConstraint(new string[] { "POST" }), + new BooleanConstraint() { Pass = true, }, }, Parameters = new List(), }; @@ -336,6 +337,76 @@ namespace Microsoft.AspNetCore.Mvc.Routing Assert.False(candidateSet[2].IsValidCandidate); } + [Fact] + public void ShouldRunActionConstraints_IgnoresIgnorableConstraints() + { + // Arrange + var actions = new ActionDescriptor[] + { + new ActionDescriptor() + { + + }, + new ActionDescriptor() + { + ActionConstraints = new List() + { + new HttpMethodActionConstraint(new[]{ "GET", }), + }, + }, + new ActionDescriptor() + { + ActionConstraints = new List() + { + new ConsumesAttribute("text/json"), + }, + }, + new ActionDescriptor() + { + ActionConstraints = new List() + { + new CorsHttpMethodActionConstraint(new HttpMethodActionConstraint(new[]{ "GET", })), + }, + }, + }; + + var selector = CreateSelector(actions); + + // Act + var result = selector.ShouldRunActionConstraints; + + // Assert + Assert.False(result); + } + + [Fact] + public void ShouldRunActionConstraints_RunsForArbitraryActionConstraint() + { + // Arrange + var actions = new ActionDescriptor[] + { + new ActionDescriptor() + { + + }, + new ActionDescriptor() + { + ActionConstraints = new List() + { + new BooleanConstraint(), + }, + }, + }; + + var selector = CreateSelector(actions); + + // Act + var result = selector.ShouldRunActionConstraints; + + // Assert + Assert.True(result); + } + private ActionConstraintMatcherPolicy CreateSelector(ActionDescriptor[] actions) { // We need to actually provide some actions with some action constraints metadata