Add StartupAnalzyer (#9100)

This commit is contained in:
James Newton-King 2019-04-06 18:25:19 +13:00 committed by GitHub
parent 4ac9001f6e
commit 69f4b6d227
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1081 additions and 0 deletions

View File

@ -52,5 +52,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
helpLinkUri: "https://aka.ms/AA20pbc");
// MVC1005 reserved for startup
}
}

View File

@ -0,0 +1,18 @@
// 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.Analyzers
{
internal abstract class ConfigureMethodAnalysis : StartupComputedAnalysis
{
protected ConfigureMethodAnalysis(IMethodSymbol configureMethod)
: base(configureMethod.ContainingType)
{
ConfigureMethod = configureMethod;
}
public IMethodSymbol ConfigureMethod { get; }
}
}

View File

@ -0,0 +1,18 @@
// 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.Analyzers
{
internal abstract class ConfigureServicesMethodAnalysis : StartupComputedAnalysis
{
protected ConfigureServicesMethodAnalysis(IMethodSymbol configureServicesMethod)
: base(configureServicesMethod.ContainingType)
{
ConfigureServicesMethod = configureServicesMethod;
}
public IMethodSymbol ConfigureServicesMethod { get; }
}
}

View File

@ -0,0 +1,53 @@
// 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.Operations;
namespace Microsoft.AspNetCore.Analyzers
{
internal class MiddlewareAnalysis : ConfigureMethodAnalysis
{
public static MiddlewareAnalysis CreateAndInitialize(StartupAnalysisContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var symbols = context.StartupSymbols;
var analysis = new MiddlewareAnalysis((IMethodSymbol)context.OperationBlockStartAnalysisContext.OwningSymbol);
var middleware = ImmutableArray.CreateBuilder<MiddlewareItem>();
context.OperationBlockStartAnalysisContext.RegisterOperationAction(context =>
{
// We're looking for usage of extension methods, so we need to look at the 'this' parameter
// rather than invocation.Instance.
if (context.Operation is IInvocationOperation invocation &&
invocation.Instance == null &&
invocation.Arguments.Length >= 1 &&
invocation.Arguments[0].Parameter?.Type == symbols.IApplicationBuilder)
{
middleware.Add(new MiddlewareItem(invocation));
}
}, OperationKind.Invocation);
context.OperationBlockStartAnalysisContext.RegisterOperationBlockEndAction(context =>
{
analysis.Middleware = middleware.ToImmutable();
});
return analysis;
}
public MiddlewareAnalysis(IMethodSymbol configureMethod)
: base(configureMethod)
{
}
public ImmutableArray<MiddlewareItem> Middleware { get; private set; } = ImmutableArray<MiddlewareItem>.Empty;
}
}

View File

@ -0,0 +1,20 @@
// 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.Operations;
namespace Microsoft.AspNetCore.Analyzers
{
internal class MiddlewareItem
{
public MiddlewareItem(IInvocationOperation operation)
{
Operation = operation;
}
public IInvocationOperation Operation { get; }
public IMethodSymbol UseMethod => Operation.TargetMethod;
}
}

View File

@ -0,0 +1,45 @@
// 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 Microsoft.AspNetCore.Mvc.Analyzers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
namespace Microsoft.AspNetCore.Analyzers
{
internal class MvcOptionsAnalysis : ConfigureServicesMethodAnalysis
{
public static MvcOptionsAnalysis CreateAndInitialize(StartupAnalysisContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var analysis = new MvcOptionsAnalysis((IMethodSymbol)context.OperationBlockStartAnalysisContext.OwningSymbol);
context.OperationBlockStartAnalysisContext.RegisterOperationAction(context =>
{
if (context.Operation is ISimpleAssignmentOperation operation &&
operation.Value.ConstantValue.HasValue &&
operation.Target is IPropertyReferenceOperation property &&
property.Member?.Name == SymbolNames.EnableEndpointRoutingProperty)
{
analysis.EndpointRoutingEnabled = operation.Value.ConstantValue.Value as bool?;
}
}, OperationKind.SimpleAssignment);
return analysis;
}
public MvcOptionsAnalysis(IMethodSymbol configureServicesMethod)
: base(configureServicesMethod)
{
}
public bool? EndpointRoutingEnabled { get; private set; }
}
}

View File

@ -0,0 +1,52 @@
// 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.Operations;
namespace Microsoft.AspNetCore.Analyzers
{
internal class ServicesAnalysis : ConfigureServicesMethodAnalysis
{
public static ServicesAnalysis CreateAndInitialize(StartupAnalysisContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var symbols = context.StartupSymbols;
var analysis = new ServicesAnalysis((IMethodSymbol)context.OperationBlockStartAnalysisContext.OwningSymbol);
var services = ImmutableArray.CreateBuilder<ServicesItem>();
context.OperationBlockStartAnalysisContext.RegisterOperationAction(context =>
{
// We're looking for usage of extension methods, so we need to look at the 'this' parameter
// rather than invocation.Instance.
if (context.Operation is IInvocationOperation invocation &&
invocation.Instance == null &&
invocation.Arguments.Length >= 1 &&
invocation.Arguments[0].Parameter?.Type == symbols.IServiceCollection)
{
services.Add(new ServicesItem(invocation));
}
}, OperationKind.Invocation);
context.OperationBlockStartAnalysisContext.RegisterOperationBlockEndAction(context =>
{
analysis.Services = services.ToImmutable();
});
return analysis;
}
public ServicesAnalysis(IMethodSymbol configureServicesMethod)
: base(configureServicesMethod)
{
}
public ImmutableArray<ServicesItem> Services { get; private set; } = ImmutableArray<ServicesItem>.Empty;
}
}

View File

@ -0,0 +1,20 @@
// 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.Operations;
namespace Microsoft.AspNetCore.Analyzers
{
internal class ServicesItem
{
public ServicesItem(IInvocationOperation operation)
{
Operation = operation;
}
public IInvocationOperation Operation { get; }
public IMethodSymbol UseMethod => Operation.TargetMethod;
}
}

View File

@ -0,0 +1,20 @@
// 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.Diagnostics;
namespace Microsoft.AspNetCore.Analyzers
{
internal class StartupAnalysisContext
{
public StartupAnalysisContext(OperationBlockStartAnalysisContext operationBlockStartAnalysisContext, StartupSymbols startupSymbols)
{
OperationBlockStartAnalysisContext = operationBlockStartAnalysisContext;
StartupSymbols = startupSymbols;
}
public OperationBlockStartAnalysisContext OperationBlockStartAnalysisContext { get; }
public StartupSymbols StartupSymbols { get; }
}
}

View File

@ -0,0 +1,20 @@
// 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.Analyzers
{
public partial class StartupAnalzyer : DiagnosticAnalyzer
{
internal readonly static DiagnosticDescriptor UnsupportedUseMvcWithEndpointRouting = new DiagnosticDescriptor(
"MVC1005",
"Cannot use UseMvc with Endpoint Routing.",
"Using '{0}' to configure MVC is not supported while using Endpoint Routing. To continue using '{0}', please set 'MvcOptions.EnableEndpointRounting = false' inside '{1}'.",
"Usage",
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
helpLinkUri: "https://aka.ms/YJggeFn");
}
}

View File

@ -0,0 +1,33 @@
// 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 Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.AspNetCore.Analyzers
{
public partial class StartupAnalzyer : DiagnosticAnalyzer
{
internal event EventHandler<StartupComputedAnalysis> AnalysisStarted;
private void OnAnalysisStarted(StartupComputedAnalysis analysis)
{
AnalysisStarted?.Invoke(this, analysis);
}
internal event EventHandler<IMethodSymbol> ConfigureServicesMethodFound;
private void OnConfigureServicesMethodFound(IMethodSymbol method)
{
ConfigureServicesMethodFound?.Invoke(this, method);
}
internal event EventHandler<IMethodSymbol> ConfigureMethodFound;
private void OnConfigureMethodFound(IMethodSymbol method)
{
ConfigureMethodFound?.Invoke(this, method);
}
}
}

View File

@ -0,0 +1,148 @@
// 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.Concurrent;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.AspNetCore.Analyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public partial class StartupAnalzyer : DiagnosticAnalyzer
{
#pragma warning disable RS1008 // Avoid storing per-compilation data into the fields of a diagnostic analyzer.
private readonly static Func<StartupAnalysisContext, StartupComputedAnalysis>[] ConfigureServicesMethodAnalysisFactories = new Func<StartupAnalysisContext, StartupComputedAnalysis>[]
{
ServicesAnalysis.CreateAndInitialize,
MvcOptionsAnalysis.CreateAndInitialize,
};
private readonly static Func<StartupAnalysisContext, StartupComputedAnalysis>[] ConfigureMethodAnalysisFactories = new Func<StartupAnalysisContext, StartupComputedAnalysis>[]
{
MiddlewareAnalysis.CreateAndInitialize,
};
private readonly static Func<CompilationAnalysisContext, ConcurrentBag<StartupComputedAnalysis>, StartupDiagnosticValidator>[] DiagnosticValidatorFactories = new Func<CompilationAnalysisContext, ConcurrentBag<StartupComputedAnalysis>, StartupDiagnosticValidator>[]
{
UseMvcDiagnosticValidator.CreateAndInitialize,
};
#pragma warning restore RS1008 // Avoid storing per-compilation data into the fields of a diagnostic analyzer.
public StartupAnalzyer()
{
SupportedDiagnostics = ImmutableArray.Create<DiagnosticDescriptor>(new[]
{
UnsupportedUseMvcWithEndpointRouting,
});
// By default the analyzer will only run for files ending with Startup.cs
// Can be overriden for unit testing other file names
// Analzyer only runs for C# so limiting to *.cs file is fine
StartupFilePredicate = path => path.EndsWith("Startup.cs", StringComparison.OrdinalIgnoreCase);
}
internal Func<string, bool> StartupFilePredicate { get; set; }
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterCompilationStartAction(OnCompilationStart);
}
private void OnCompilationStart(CompilationStartAnalysisContext context)
{
var symbols = new StartupSymbols(context.Compilation);
// Don't run analyzer if ASP.NET Core types cannot be found
if (symbols.IServiceCollection == null || symbols.IApplicationBuilder == null)
{
return;
}
// This analyzer is a general-purpose framework that functions by:
// 1. Discovering Startup methods (ConfigureServices, Configure)
// 2. Launching additional analyses of these Startup methods and collecting results
// 3. Running a final pass to add diagnostics based on computed state
var analyses = new ConcurrentBag<StartupComputedAnalysis>();
context.RegisterOperationBlockStartAction(context =>
{
AnalyzeStartupMethods(context, symbols, analyses);
});
// Run after analyses have had a chance to finish to add diagnostics.
context.RegisterCompilationEndAction(analysisContext =>
{
RunAnalysis(analysisContext, analyses);
});
}
private static void RunAnalysis(CompilationAnalysisContext analysisContext, ConcurrentBag<StartupComputedAnalysis> analyses)
{
for (var i = 0; i < DiagnosticValidatorFactories.Length; i++)
{
var validator = DiagnosticValidatorFactories[i].Invoke(analysisContext, analyses);
}
}
private void AnalyzeStartupMethods(OperationBlockStartAnalysisContext context, StartupSymbols symbols, ConcurrentBag<StartupComputedAnalysis> analyses)
{
if (!IsStartupFile(context))
{
return;
}
if (context.OwningSymbol.Kind != SymbolKind.Method)
{
return;
}
var startupAnalysisContext = new StartupAnalysisContext(context, symbols);
var method = (IMethodSymbol)context.OwningSymbol;
if (StartupFacts.IsConfigureServices(symbols, method))
{
for (var i = 0; i < ConfigureServicesMethodAnalysisFactories.Length; i++)
{
var analysis = ConfigureServicesMethodAnalysisFactories[i].Invoke(startupAnalysisContext);
analyses.Add(analysis);
OnAnalysisStarted(analysis);
}
OnConfigureServicesMethodFound(method);
}
if (StartupFacts.IsConfigure(symbols, method))
{
for (var i = 0; i < ConfigureMethodAnalysisFactories.Length; i++)
{
var analysis = ConfigureMethodAnalysisFactories[i].Invoke(startupAnalysisContext);
analyses.Add(analysis);
OnAnalysisStarted(analysis);
}
OnConfigureMethodFound(method);
}
}
private bool IsStartupFile(OperationBlockStartAnalysisContext context)
{
foreach (var location in context.OwningSymbol.Locations)
{
if (location.IsInSource && StartupFilePredicate(location.SourceTree.FilePath))
{
return true;
}
}
return false;
}
}
}

View File

@ -0,0 +1,17 @@
// 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.Analyzers
{
internal abstract class StartupComputedAnalysis
{
protected StartupComputedAnalysis(INamedTypeSymbol enclosingType)
{
EnclosingType = enclosingType;
}
public INamedTypeSymbol EnclosingType { get; }
}
}

View File

@ -0,0 +1,9 @@
// 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.Analyzers
{
internal abstract class StartupDiagnosticValidator
{
}
}

View File

@ -0,0 +1,71 @@
// 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 Microsoft.AspNetCore.Mvc.Analyzers;
using Microsoft.CodeAnalysis;
namespace Microsoft.AspNetCore.Analyzers
{
internal static class StartupFacts
{
public static bool IsConfigureServices(StartupSymbols symbols, IMethodSymbol symbol)
{
if (symbol == null)
{
throw new ArgumentNullException(nameof(symbol));
}
if (symbol.DeclaredAccessibility != Accessibility.Public)
{
return false;
}
if (!string.Equals(symbol.Name, SymbolNames.ConfigureServicesMethod, StringComparison.Ordinal))
{
return false;
}
if (symbol.Parameters.Length != 1)
{
return false;
}
if (symbol.Parameters[0].Type != symbols.IServiceCollection)
{
return false;
}
return true;
}
public static bool IsConfigure(StartupSymbols symbols, IMethodSymbol symbol)
{
if (symbol == null)
{
throw new ArgumentNullException(nameof(symbol));
}
if (symbol.DeclaredAccessibility != Accessibility.Public)
{
return false;
}
if (symbol.Name == null || !symbol.Name.StartsWith(SymbolNames.ConfigureMethod, StringComparison.Ordinal))
{
return false;
}
// IApplicationBuilder can appear in any parameter
for (var i = 0; i < symbol.Parameters.Length; i++)
{
if (symbol.Parameters[i].Type == symbols.IApplicationBuilder)
{
return true;
}
}
return false;
}
}
}

View File

@ -0,0 +1,24 @@
// 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.AspNetCore.Mvc.Analyzers;
using Microsoft.CodeAnalysis;
namespace Microsoft.AspNetCore.Analyzers
{
internal class StartupSymbols
{
public StartupSymbols(Compilation compilation)
{
IApplicationBuilder = compilation.GetTypeByMetadataName(SymbolNames.IApplicationBuilder);
IServiceCollection = compilation.GetTypeByMetadataName(SymbolNames.IServiceCollection);
MvcOptions = compilation.GetTypeByMetadataName(SymbolNames.MvcOptions);
}
public INamedTypeSymbol IApplicationBuilder { get; }
public INamedTypeSymbol IServiceCollection { get; }
public INamedTypeSymbol MvcOptions { get; }
}
}

View File

@ -0,0 +1,51 @@
// 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.Concurrent;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.AspNetCore.Analyzers
{
internal class UseMvcDiagnosticValidator : StartupDiagnosticValidator
{
public static UseMvcDiagnosticValidator CreateAndInitialize(CompilationAnalysisContext context, ConcurrentBag<StartupComputedAnalysis> analyses)
{
if (analyses == null)
{
throw new ArgumentNullException(nameof(analyses));
}
var validator = new UseMvcDiagnosticValidator();
foreach (var mvcOptionsAnalysis in analyses.OfType<MvcOptionsAnalysis>())
{
// Each analysis of the options is one-per-class (in user code). Find the middleware analysis foreach of the Configure methods
// defined by this class and validate.
//
// Note that this doesn't attempt to handle inheritance scenarios.
foreach (var middlewareAnalsysis in analyses.OfType<MiddlewareAnalysis>().Where(m => m.EnclosingType == mvcOptionsAnalysis.EnclosingType))
{
foreach (var middlewareItem in middlewareAnalsysis.Middleware)
{
if ((middlewareItem.UseMethod.Name == "UseMvc" || middlewareItem.UseMethod.Name == "UseMvcWithDefaultRoute") &&
// Report a diagnostic if it's unclear that the user turned off Endpoint Routing.
(mvcOptionsAnalysis.EndpointRoutingEnabled == true || mvcOptionsAnalysis.EndpointRoutingEnabled == null))
{
context.ReportDiagnostic(Diagnostic.Create(
StartupAnalzyer.UnsupportedUseMvcWithEndpointRouting,
middlewareItem.Operation.Syntax.GetLocation(),
middlewareItem.UseMethod.Name,
mvcOptionsAnalysis.ConfigureServicesMethod.Name));
}
}
}
}
return validator;
}
}
}

View File

@ -60,5 +60,17 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
public const string ProducesResponseTypeAttribute = "Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute";
public const string RenderPartialMethod = "RenderPartial";
public const string IApplicationBuilder = "Microsoft.AspNetCore.Builder.IApplicationBuilder";
public const string IServiceCollection = "Microsoft.Extensions.DependencyInjection.IServiceCollection";
public const string MvcOptions = "Microsoft.AspNetCore.Mvc.MvcOptions";
public const string EnableEndpointRoutingProperty = "EnableEndpointRouting";
public const string ConfigureServicesMethod = "ConfigureServices";
public const string ConfigureMethod = "Configure";
}
}

View File

@ -0,0 +1,218 @@
// 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.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Analyzer.Testing;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis;
using Xunit;
namespace Microsoft.AspNetCore.Analyzers
{
public class StartupAnalyzerTest
{
public StartupAnalyzerTest()
{
StartupAnalyzer = new StartupAnalzyer();
StartupAnalyzer.StartupFilePredicate = path => path.Equals("Test.cs", StringComparison.Ordinal);
Runner = new MvcDiagnosticAnalyzerRunner(StartupAnalyzer);
Analyses = new ConcurrentBag<StartupComputedAnalysis>();
ConfigureServicesMethods = new ConcurrentBag<IMethodSymbol>();
ConfigureMethods = new ConcurrentBag<IMethodSymbol>();
StartupAnalyzer.AnalysisStarted += (sender, analysis) => Analyses.Add(analysis);
StartupAnalyzer.ConfigureServicesMethodFound += (sender, method) => ConfigureServicesMethods.Add(method);
StartupAnalyzer.ConfigureMethodFound += (sender, method) => ConfigureMethods.Add(method);
}
private StartupAnalzyer StartupAnalyzer { get; }
private MvcDiagnosticAnalyzerRunner Runner { get; }
private ConcurrentBag<StartupComputedAnalysis> Analyses { get; }
private ConcurrentBag<IMethodSymbol> ConfigureServicesMethods { get; }
private ConcurrentBag<IMethodSymbol> ConfigureMethods { get; }
[Fact]
public async Task StartupAnalyzer_FindsStartupMethods_StartupSignatures_Standard()
{
// Arrange
var source = ReadSource("StartupSignatures_Standard");
// Act
var diagnostics = await Runner.GetDiagnosticsAsync(source.Source);
// Assert
Assert.Empty(diagnostics);
Assert.Collection(ConfigureServicesMethods, m => Assert.Equal("ConfigureServices", m.Name));
Assert.Collection(ConfigureMethods, m => Assert.Equal("Configure", m.Name));
}
[Fact]
public async Task StartupAnalyzer_FindsStartupMethods_StartupSignatures_MoreVariety()
{
// Arrange
var source = ReadSource("StartupSignatures_MoreVariety");
// Act
var diagnostics = await Runner.GetDiagnosticsAsync(source.Source);
// Assert
Assert.Empty(diagnostics);
Assert.Collection(
ConfigureServicesMethods.OrderBy(m => m.Name),
m => Assert.Equal("ConfigureServices", m.Name));
Assert.Collection(
ConfigureMethods.OrderBy(m => m.Name),
m => Assert.Equal("Configure", m.Name),
m => Assert.Equal("ConfigureProduction", m.Name));
}
[Fact]
public async Task StartupAnalyzer_MvcOptionsAnalysis_UseMvc_FindsEndpointRoutingDisabled()
{
// Arrange
var source = ReadSource("MvcOptions_UseMvcWithDefaultRouteAndEndpointRoutingDisabled");
// Act
var diagnostics = await Runner.GetDiagnosticsAsync(source.Source);
// Assert
var mvcOptionsAnalysis = Assert.Single(Analyses.OfType<MvcOptionsAnalysis>());
Assert.False(mvcOptionsAnalysis.EndpointRoutingEnabled);
var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>());
var middleware = Assert.Single(middlewareAnalysis.Middleware);
Assert.Equal("UseMvcWithDefaultRoute", middleware.UseMethod.Name);
Assert.Empty(diagnostics);
}
[Fact]
public async Task StartupAnalyzer_MvcOptionsAnalysis_AddMvcOptions_FindsEndpointRoutingDisabled()
{
// Arrange
var source = ReadSource("MvcOptions_UseMvcWithDefaultRouteAndAddMvcOptionsEndpointRoutingDisabled");
// Act
var diagnostics = await Runner.GetDiagnosticsAsync(source.Source);
// Assert
var mvcOptionsAnalysis = Assert.Single(Analyses.OfType<MvcOptionsAnalysis>());
Assert.False(mvcOptionsAnalysis.EndpointRoutingEnabled);
var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>());
var middleware = Assert.Single(middlewareAnalysis.Middleware);
Assert.Equal("UseMvcWithDefaultRoute", middleware.UseMethod.Name);
Assert.Empty(diagnostics);
}
[Theory]
[InlineData("MvcOptions_UseMvc", "UseMvc")]
[InlineData("MvcOptions_UseMvcAndConfiguredRoutes", "UseMvc")]
[InlineData("MvcOptions_UseMvcWithDefaultRoute", "UseMvcWithDefaultRoute")]
public async Task StartupAnalyzer_MvcOptionsAnalysis_FindsEndpointRoutingEnabled(string sourceFileName, string mvcMiddlewareName)
{
// Arrange
var source = ReadSource(sourceFileName);
// Act
var diagnostics = await Runner.GetDiagnosticsAsync(source.Source);
// Assert
var mvcOptionsAnalysis = Assert.Single(Analyses.OfType<MvcOptionsAnalysis>());
Assert.Null(mvcOptionsAnalysis.EndpointRoutingEnabled);
var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>());
var middleware = Assert.Single(middlewareAnalysis.Middleware);
Assert.Equal(mvcMiddlewareName, middleware.UseMethod.Name);
Assert.Collection(
diagnostics,
diagnostic =>
{
Assert.Same(StartupAnalzyer.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor);
AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location);
});
}
[Fact]
public async Task StartupAnalyzer_MvcOptionsAnalysis_MultipleMiddleware()
{
// Arrange
var source = ReadSource("MvcOptions_UseMvcWithOtherMiddleware");
// Act
var diagnostics = await Runner.GetDiagnosticsAsync(source.Source);
// Assert
var mvcOptionsAnalysis = Assert.Single(Analyses.OfType<MvcOptionsAnalysis>());
Assert.Null(mvcOptionsAnalysis.EndpointRoutingEnabled);
var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>());
Assert.Collection(
middlewareAnalysis.Middleware,
item => Assert.Equal("UseAuthorization", item.UseMethod.Name),
item => Assert.Equal("UseMiddleware", item.UseMethod.Name),
item => Assert.Equal("UseMvc", item.UseMethod.Name),
item => Assert.Equal("UseRouting", item.UseMethod.Name),
item => Assert.Equal("UseEndpoints", item.UseMethod.Name));
Assert.Collection(
diagnostics,
diagnostic =>
{
Assert.Same(StartupAnalzyer.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor);
AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location);
});
}
[Fact]
public async Task StartupAnalyzer_MvcOptionsAnalysis_MultipleUseMvc()
{
// Arrange
var source = ReadSource("MvcOptions_UseMvcMultiple");
// Act
var diagnostics = await Runner.GetDiagnosticsAsync(source.Source);
// Assert
var mvcOptionsAnalysis = Assert.Single(Analyses.OfType<MvcOptionsAnalysis>());
Assert.Null(mvcOptionsAnalysis.EndpointRoutingEnabled);
Assert.Collection(
diagnostics,
diagnostic =>
{
Assert.Same(StartupAnalzyer.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor);
AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM1"], diagnostic.Location);
},
diagnostic =>
{
Assert.Same(StartupAnalzyer.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor);
AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM2"], diagnostic.Location);
},
diagnostic =>
{
Assert.Same(StartupAnalzyer.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor);
AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM3"], diagnostic.Location);
});
}
private TestSource ReadSource(string fileName)
{
return MvcTestSource.Read(nameof(StartupAnalyzerTest), fileName);
}
}
}

View File

@ -0,0 +1,21 @@
// 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.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Analyzers.TestFiles.StartupAnalyzerTest
{
public class MvcOptions_UseMvc
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
/*MM*/app.UseMvc();
}
}
}

View File

@ -0,0 +1,24 @@
// 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.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Analyzers.TestFiles.StartupAnalyzerTest
{
public class MvcOptions_UseMvcAndConfiguredRoutes
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
/*MM*/app.UseMvc(routes =>
{
routes.MapRoute("Name", "Template");
});
}
}
}

View File

@ -0,0 +1,34 @@
// 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.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Analyzers.TestFiles.StartupAnalyzerTest
{
public class MvcOptions_UseMvcMultiple
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
/*MM1*/app.UseMvcWithDefaultRoute();
app.UseAuthorization();
app.UseMiddleware<AuthorizationMiddleware>();
/*MM2*/app.UseMvc();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
});
/*MM3*/app.UseMvc();
}
}
}

View File

@ -0,0 +1,21 @@
// 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.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Analyzers.TestFiles.StartupAnalyzerTest
{
public class MvcOptions_UseMvcWithDefaultRoute
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
/*MM*/app.UseMvcWithDefaultRoute();
}
}
}

View File

@ -0,0 +1,21 @@
// 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.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Analyzers.TestFiles.StartupAnalyzerTest
{
public class MvcOptions_UseMvcWithDefaultRouteAndAddMvcOptionsEndpointRoutingDisabled
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddMvcOptions(options => options.EnableEndpointRouting = false);
}
public void Configure(IApplicationBuilder app)
{
app.UseMvcWithDefaultRoute();
}
}
}

View File

@ -0,0 +1,21 @@
// 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.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Analyzers.TestFiles.StartupAnalyzerTest
{
public class MvcOptions_UseMvcWithDefaultRouteAndEndpointRoutingDisabled
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => options.EnableEndpointRouting = false);
}
public void Configure(IApplicationBuilder app)
{
app.UseMvcWithDefaultRoute();
}
}
}

View File

@ -0,0 +1,30 @@
// 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.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Analyzers.TestFiles.StartupAnalyzerTest
{
public class MvcOptions_UseMvcWithOtherMiddleware
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseAuthorization();
app.UseMiddleware<AuthorizationMiddleware>();
/*MM*/app.UseMvc();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
});
}
}
}

View File

@ -0,0 +1,37 @@
// 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.Text;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Analyzers.TestFiles.StartupAnalyzerTest
{
public class StartupSignatures_MoreVariety
{
public void ConfigureServices(IServiceCollection services)
{
}
public void ConfigureServices(IServiceCollection services, StringBuilder s) // Ignored
{
}
public void Configure(StringBuilder s) // Ignored,
{
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
}
public void ConfigureProduction(IWebHostEnvironment env, IApplicationBuilder app)
{
}
private void Configure(IApplicationBuilder app) // Ignored
{
}
}
}

View File

@ -0,0 +1,20 @@
// 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.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Analyzers.TestFiles.StartupAnalyzerTest
{
public class StartupSignatures_Standard
{
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app)
{
}
}
}