Remove experimental analyzers
This commit is contained in:
parent
9895593926
commit
013697ad89
|
|
@ -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}
|
||||
|
|
|
|||
30
Mvc.sln
30
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}
|
||||
|
|
|
|||
|
|
@ -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<string, string>(StringComparer.Ordinal)
|
||||
.Add(ReturnTypeKey, returnType);
|
||||
|
||||
var location = methodSyntax.ReturnType.GetLocation();
|
||||
context.ReportDiagnostic(Diagnostic.Create(
|
||||
SupportedDiagnostic,
|
||||
location,
|
||||
properties: properties));
|
||||
|
||||
}, SyntaxKind.MethodDeclaration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<string> 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<Document> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<string, string>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<string> 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<Document> 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<T>.
|
||||
isTaskOActionResult = true;
|
||||
declaredReturnType = namedReturnType.TypeArguments[0];
|
||||
}
|
||||
|
||||
if (!declaredReturnType.IsAssignableFrom(analyzerContext.IActionResult))
|
||||
{
|
||||
// Method signature does not look like IActionResult MyAction or SomeAwaitable<IActionResult>.
|
||||
// Nothing to do here.
|
||||
return;
|
||||
}
|
||||
|
||||
// Method returns an IActionResult. Determine if the method block returns an ObjectResult
|
||||
foreach (var returnStatement in methodSyntax.DescendantNodes().OfType<ReturnStatementSyntax>())
|
||||
{
|
||||
var returnType = context.SemanticModel.GetTypeInfo(returnStatement.Expression, context.CancellationToken);
|
||||
if (returnType.Type == null || returnType.Type.Kind == SymbolKind.ErrorType)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ImmutableDictionary<string, string> 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<string, string>(StringComparer.Ordinal)
|
||||
.Add(ReturnTypeKey, desiredReturnTypeString);
|
||||
}
|
||||
|
||||
context.ReportDiagnostic(Diagnostic.Create(
|
||||
SupportedDiagnostic,
|
||||
methodSyntax.ReturnType.GetLocation(),
|
||||
properties: properties));
|
||||
}
|
||||
}
|
||||
}, SyntaxKind.MethodDeclaration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<string> 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<Document> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<DiagnosticDescriptor> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<DiagnosticDescriptor> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<T>.",
|
||||
"Actions on types annotated with ApiControllerAttribute should return ActionResult<T>.",
|
||||
"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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Description>CSharp Analyzers for ASP.NET Core MVC.</Description>
|
||||
<PackageTags>aspnetcore;aspnetcoremvc</PackageTags>
|
||||
|
||||
<VerifyVersion>false</VerifyVersion>
|
||||
<VersionPrefix>$(ExperimentalVersionPrefix)</VersionPrefix>
|
||||
<VersionSuffix>$(ExperimentalVersionSuffix)</VersionSuffix>
|
||||
<PackageVersion>$(ExperimentalPackageVersion)</PackageVersion>
|
||||
|
||||
<TargetFramework>netstandard1.3</TargetFramework>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
<EnableApiCheck>false</EnableApiCheck>
|
||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<SignedPackageFile Include="analyzers/dotnet/cs/$(TargetFileName)" Certificate="$(AssemblySigningCertName)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="$(OutputPath)$(AssemblyName).dll" Pack="true" PackagePath="analyzers\dotnet\cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Pet>")]
|
||||
[InlineData("System.Threading.Task<Pet>")]
|
||||
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<Pet> 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<Pet> 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<IActionResult> 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<ActionResult<Pet>> 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<string, DiagnosticLocation> MarkerLocations { get; } = new Dictionary<string, DiagnosticLocation>();
|
||||
|
||||
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<MetadataReference>()
|
||||
.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<Diagnostic[]> 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<string> ApplyCodeFixAsync(
|
||||
Project project,
|
||||
Diagnostic[] analyzerDiagnostic,
|
||||
int codeFixIndex = 0)
|
||||
{
|
||||
var diagnostic = analyzerDiagnostic.Single();
|
||||
return ApplyCodeFixAsync(project, diagnostic, codeFixIndex);
|
||||
}
|
||||
|
||||
protected async Task<string> 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<CodeAction>();
|
||||
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<Solution> ApplyFixAsync(CodeAction codeAction)
|
||||
{
|
||||
var operations = await codeAction.GetOperationsAsync(CancellationToken.None);
|
||||
return Assert.Single(operations.OfType<ApplyChangesOperation>()).ChangedSolution;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Workspace?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.Analyzers.Experimental\Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj" />
|
||||
|
||||
<PackageReference Include="Microsoft.AspNetCore.Analyzer.Testing" Version="$(MicrosoftAspNetCoreAnalyzerTestingPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"shadowCopy": false
|
||||
}
|
||||
|
|
@ -9,13 +9,5 @@
|
|||
<PackageVersion Condition="'$(IsFinalBuild)' == 'true' AND '$(VersionSuffix)' != 'rtm' ">$(VersionPrefix)-$(VersionSuffix)-final</PackageVersion>
|
||||
<VersionSuffix Condition="'$(VersionSuffix)' != '' And '$(FeatureBranchVersionSuffix)' != ''">$(FeatureBranchVersionPrefix)$(VersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-'))</VersionSuffix>
|
||||
<VersionSuffix Condition="'$(VersionSuffix)' != '' And '$(BuildNumber)' != ''">$(VersionSuffix)-$(BuildNumber)</VersionSuffix>
|
||||
|
||||
<ExperimentalVersionPrefix>0.2.0</ExperimentalVersionPrefix>
|
||||
<ExperimentalVersionSuffix>preview3</ExperimentalVersionSuffix>
|
||||
|
||||
<ExperimentalPackageVersion Condition="'$(IsFinalBuild)' == 'true' AND '$(ExperimentalVersionSuffix)' == 'rtm' ">$(ExperimentalVersionPrefix)</ExperimentalPackageVersion>
|
||||
<ExperimentalPackageVersion Condition="'$(IsFinalBuild)' == 'true' AND '$(ExperimentalVersionSuffix)' != 'rtm' ">$(ExperimentalVersionPrefix)-$(ExperimentalVersionSuffix)-final</ExperimentalPackageVersion>
|
||||
<ExperimentalVersionSuffix Condition="'$(ExperimentalVersionSuffix)' != '' And '$(FeatureBranchVersionSuffix)' != ''">$(FeatureBranchVersionPrefix)$(ExperimentalVersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-'))</ExperimentalVersionSuffix>
|
||||
<ExperimentalVersionSuffix Condition="'$(ExperimentalVersionSuffix)' != '' And '$(BuildNumber)' != ''">$(ExperimentalVersionSuffix)-$(BuildNumber)</ExperimentalVersionSuffix>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
|||
Loading…
Reference in New Issue