diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/BuildServiceProviderValidator.cs b/src/Mvc/Mvc.Analyzers/src/Startup/BuildServiceProviderAnalzyer.cs similarity index 52% rename from src/Mvc/Mvc.Analyzers/src/Startup/BuildServiceProviderValidator.cs rename to src/Mvc/Mvc.Analyzers/src/Startup/BuildServiceProviderAnalzyer.cs index ef6d430eee..edd78751b9 100644 --- a/src/Mvc/Mvc.Analyzers/src/Startup/BuildServiceProviderValidator.cs +++ b/src/Mvc/Mvc.Analyzers/src/Startup/BuildServiceProviderAnalzyer.cs @@ -1,41 +1,42 @@ // 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.Diagnostics; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.AspNetCore.Analyzers { - internal class BuildServiceProviderValidator : StartupDiagnosticValidator + internal class BuildServiceProviderValidator { - public static BuildServiceProviderValidator CreateAndInitialize(CompilationAnalysisContext context, ConcurrentBag analyses) + private readonly StartupAnalysis _context; + + public BuildServiceProviderValidator(StartupAnalysis context) { - if (analyses == null) - { - throw new ArgumentNullException(nameof(analyses)); - } + _context = context; + } - var validator = new BuildServiceProviderValidator(); + public void AnalyzeSymbol(SymbolAnalysisContext context) + { + Debug.Assert(context.Symbol.Kind == SymbolKind.NamedType); + Debug.Assert(StartupFacts.IsStartupClass(_context.StartupSymbols, (INamedTypeSymbol)context.Symbol)); - foreach (var serviceAnalysis in analyses.OfType()) + var type = (INamedTypeSymbol)context.Symbol; + + foreach (var serviceAnalysis in _context.GetRelatedAnalyses(type)) { foreach (var serviceItem in serviceAnalysis.Services) { if (serviceItem.UseMethod.Name == "BuildServiceProvider") { context.ReportDiagnostic(Diagnostic.Create( - StartupAnalzyer.BuildServiceProviderShouldNotCalledInConfigureServicesMethod, + StartupAnalzyer.Diagnostics.BuildServiceProviderShouldNotCalledInConfigureServicesMethod, serviceItem.Operation.Syntax.GetLocation(), serviceItem.UseMethod.Name, serviceAnalysis.ConfigureServicesMethod.Name)); } } } - - return validator; } } } diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/ConfigureMethodAnalysis.cs b/src/Mvc/Mvc.Analyzers/src/Startup/ConfigureMethodAnalysis.cs deleted file mode 100644 index 394dd23bff..0000000000 --- a/src/Mvc/Mvc.Analyzers/src/Startup/ConfigureMethodAnalysis.cs +++ /dev/null @@ -1,18 +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.Analyzers -{ - internal abstract class ConfigureMethodAnalysis : StartupComputedAnalysis - { - protected ConfigureMethodAnalysis(IMethodSymbol configureMethod) - : base(configureMethod.ContainingType) - { - ConfigureMethod = configureMethod; - } - - public IMethodSymbol ConfigureMethod { get; } - } -} diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/MiddlewareAnalysis.cs b/src/Mvc/Mvc.Analyzers/src/Startup/MiddlewareAnalysis.cs index 874dc30ea6..789dbcc352 100644 --- a/src/Mvc/Mvc.Analyzers/src/Startup/MiddlewareAnalysis.cs +++ b/src/Mvc/Mvc.Analyzers/src/Startup/MiddlewareAnalysis.cs @@ -1,53 +1,23 @@ // 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 + internal class MiddlewareAnalysis { - public static MiddlewareAnalysis CreateAndInitialize(StartupAnalysisContext context) + public MiddlewareAnalysis(IMethodSymbol configureMethod, ImmutableArray middleware) { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - var symbols = context.StartupSymbols; - var analysis = new MiddlewareAnalysis((IMethodSymbol)context.OperationBlockStartAnalysisContext.OwningSymbol); - - var middleware = ImmutableArray.CreateBuilder(); - - 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; + ConfigureMethod = configureMethod; + Middleware = middleware; } - public MiddlewareAnalysis(IMethodSymbol configureMethod) - : base(configureMethod) - { - } + public INamedTypeSymbol StartupType => ConfigureMethod.ContainingType; - public ImmutableArray Middleware { get; private set; } = ImmutableArray.Empty; + public IMethodSymbol ConfigureMethod { get; } + + public ImmutableArray Middleware { get; } } } diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/MiddlewareAnalyzer.cs b/src/Mvc/Mvc.Analyzers/src/Startup/MiddlewareAnalyzer.cs new file mode 100644 index 0000000000..0a8deb5dc4 --- /dev/null +++ b/src/Mvc/Mvc.Analyzers/src/Startup/MiddlewareAnalyzer.cs @@ -0,0 +1,48 @@ +// 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; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.AspNetCore.Analyzers +{ + internal class MiddlewareAnalyzer + { + private readonly StartupAnalysisBuilder _context; + + public MiddlewareAnalyzer(StartupAnalysisBuilder context) + { + _context = context; + } + + public void AnalyzeConfigureMethod(OperationBlockStartAnalysisContext context) + { + var configureMethod = (IMethodSymbol)context.OwningSymbol; + var middleware = ImmutableArray.CreateBuilder(); + + // Note: this is a simple source-order implementation. We don't attempt perform data flow + // analysis in order to determine the actual order in which middleware are ordered. + // + // This can currently be confused by things like Map(...) + context.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 == _context.StartupSymbols.IApplicationBuilder) + { + middleware.Add(new MiddlewareItem(invocation)); + } + }, OperationKind.Invocation); + + context.RegisterOperationBlockEndAction(context => + { + _context.ReportAnalysis(new MiddlewareAnalysis(configureMethod, middleware.ToImmutable())); + }); + } + } +} diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/MvcOptionsAnalysis.cs b/src/Mvc/Mvc.Analyzers/src/Startup/MvcOptionsAnalysis.cs deleted file mode 100644 index d5dd63375b..0000000000 --- a/src/Mvc/Mvc.Analyzers/src/Startup/MvcOptionsAnalysis.cs +++ /dev/null @@ -1,45 +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 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; } - } -} diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/ConfigureServicesMethodAnalysis.cs b/src/Mvc/Mvc.Analyzers/src/Startup/OptionsAnalysis.cs similarity index 53% rename from src/Mvc/Mvc.Analyzers/src/Startup/ConfigureServicesMethodAnalysis.cs rename to src/Mvc/Mvc.Analyzers/src/Startup/OptionsAnalysis.cs index ebe773474b..08f0e11e95 100644 --- a/src/Mvc/Mvc.Analyzers/src/Startup/ConfigureServicesMethodAnalysis.cs +++ b/src/Mvc/Mvc.Analyzers/src/Startup/OptionsAnalysis.cs @@ -1,18 +1,23 @@ // 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; namespace Microsoft.AspNetCore.Analyzers { - internal abstract class ConfigureServicesMethodAnalysis : StartupComputedAnalysis + internal class OptionsAnalysis { - protected ConfigureServicesMethodAnalysis(IMethodSymbol configureServicesMethod) - : base(configureServicesMethod.ContainingType) + public OptionsAnalysis(IMethodSymbol configureServicesMethod, ImmutableArray options) { ConfigureServicesMethod = configureServicesMethod; + Options = options; } + public INamedTypeSymbol StartupType => ConfigureServicesMethod.ContainingType; + public IMethodSymbol ConfigureServicesMethod { get; } + + public ImmutableArray Options { get; } } } diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/OptionsAnalyzer.cs b/src/Mvc/Mvc.Analyzers/src/Startup/OptionsAnalyzer.cs new file mode 100644 index 0000000000..a6649c0194 --- /dev/null +++ b/src/Mvc/Mvc.Analyzers/src/Startup/OptionsAnalyzer.cs @@ -0,0 +1,43 @@ +// 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; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.AspNetCore.Analyzers +{ + internal class OptionsAnalyzer + { + private readonly StartupAnalysisBuilder _context; + + public OptionsAnalyzer(StartupAnalysisBuilder context) + { + _context = context; + } + + public void AnalyzeConfigureServices(OperationBlockStartAnalysisContext context) + { + var configureServicesMethod = (IMethodSymbol)context.OwningSymbol; + var options = ImmutableArray.CreateBuilder(); + context.RegisterOperationAction(context => + { + if (context.Operation is ISimpleAssignmentOperation operation && + operation.Value.ConstantValue.HasValue && + operation.Target is IPropertyReferenceOperation property && + property.Property?.ContainingType?.Name != null && + property.Property.ContainingType.Name.EndsWith("Options")) + { + options.Add(new OptionsItem(property.Property, operation.Value.ConstantValue.Value)); + } + + }, OperationKind.SimpleAssignment); + + context.RegisterOperationBlockEndAction(context => + { + _context.ReportAnalysis(new OptionsAnalysis(configureServicesMethod, options.ToImmutable())); + }); + } + } +} diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/OptionsFacts.cs b/src/Mvc/Mvc.Analyzers/src/Startup/OptionsFacts.cs new file mode 100644 index 0000000000..da95216f73 --- /dev/null +++ b/src/Mvc/Mvc.Analyzers/src/Startup/OptionsFacts.cs @@ -0,0 +1,27 @@ +// 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 OptionsFacts + { + public static bool IsEndpointRoutingExplicitlyDisabled(OptionsAnalysis analysis) + { + for (var i = 0; i < analysis.Options.Length; i++) + { + var item = analysis.Options[i]; + if (string.Equals(item.OptionsType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat), SymbolNames.MvcOptions) && + string.Equals(item.Property.Name, SymbolNames.EnableEndpointRoutingProperty, StringComparison.Ordinal)) + { + return item.ConstantValue as bool? == false; + } + } + + return false; + } + } +} diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/OptionsItem.cs b/src/Mvc/Mvc.Analyzers/src/Startup/OptionsItem.cs new file mode 100644 index 0000000000..d39850339f --- /dev/null +++ b/src/Mvc/Mvc.Analyzers/src/Startup/OptionsItem.cs @@ -0,0 +1,22 @@ +// 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 class OptionsItem + { + public OptionsItem(IPropertySymbol property, object constantValue) + { + Property = property; + ConstantValue = constantValue; + } + + public INamedTypeSymbol OptionsType => Property.ContainingType; + + public IPropertySymbol Property { get; } + + public object ConstantValue { get; } + } +} diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/ServicesAnalysis.cs b/src/Mvc/Mvc.Analyzers/src/Startup/ServicesAnalysis.cs index c0499c9bf9..2cb58ca5d5 100644 --- a/src/Mvc/Mvc.Analyzers/src/Startup/ServicesAnalysis.cs +++ b/src/Mvc/Mvc.Analyzers/src/Startup/ServicesAnalysis.cs @@ -1,52 +1,23 @@ // 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 + internal class ServicesAnalysis { - public static ServicesAnalysis CreateAndInitialize(StartupAnalysisContext context) + public ServicesAnalysis(IMethodSymbol configureServicesMethod, ImmutableArray services) { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - var symbols = context.StartupSymbols; - var analysis = new ServicesAnalysis((IMethodSymbol)context.OperationBlockStartAnalysisContext.OwningSymbol); - - var services = ImmutableArray.CreateBuilder(); - 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; + ConfigureServicesMethod = configureServicesMethod; + Services = services; } - public ServicesAnalysis(IMethodSymbol configureServicesMethod) - : base(configureServicesMethod) - { - } + public INamedTypeSymbol StartupType => ConfigureServicesMethod.ContainingType; - public ImmutableArray Services { get; private set; } = ImmutableArray.Empty; + public IMethodSymbol ConfigureServicesMethod { get; } + + public ImmutableArray Services { get; } } } diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/ServicesAnalyzer.cs b/src/Mvc/Mvc.Analyzers/src/Startup/ServicesAnalyzer.cs new file mode 100644 index 0000000000..dc79cf4727 --- /dev/null +++ b/src/Mvc/Mvc.Analyzers/src/Startup/ServicesAnalyzer.cs @@ -0,0 +1,43 @@ +// 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; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.AspNetCore.Analyzers +{ + internal class ServicesAnalyzer + { + private readonly StartupAnalysisBuilder _context; + + public ServicesAnalyzer(StartupAnalysisBuilder context) + { + _context = context; + } + + public void AnalyzeConfigureServices(OperationBlockStartAnalysisContext context) + { + var configureServicesMethod = (IMethodSymbol)context.OwningSymbol; + var services = ImmutableArray.CreateBuilder(); + context.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 == _context.StartupSymbols.IServiceCollection) + { + services.Add(new ServicesItem(invocation)); + } + }, OperationKind.Invocation); + + context.RegisterOperationBlockEndAction(context => + { + _context.ReportAnalysis(new ServicesAnalysis(configureServicesMethod, services.ToImmutable())); + }); + } + } +} diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalysis.cs b/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalysis.cs new file mode 100644 index 0000000000..9ddc03e04e --- /dev/null +++ b/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalysis.cs @@ -0,0 +1,56 @@ +// 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; + +namespace Microsoft.AspNetCore.Analyzers +{ + internal class StartupAnalysis + { + private ImmutableDictionary> _analysesByType; + + public StartupAnalysis( + StartupSymbols startupSymbols, + ImmutableDictionary> analysesByType) + { + StartupSymbols = startupSymbols; + _analysesByType = analysesByType; + } + + public StartupSymbols StartupSymbols { get; } + + public T? GetRelatedSingletonAnalysis(INamedTypeSymbol type) where T : class + { + if (_analysesByType.TryGetValue(type, out var list)) + { + for (var i = 0; i < list.Length; i++) + { + if (list[i] is T item) + { + return item; + } + } + } + + return null; + } + + public ImmutableArray GetRelatedAnalyses(INamedTypeSymbol type) where T : class + { + var items = ImmutableArray.CreateBuilder(); + if (_analysesByType.TryGetValue(type, out var list)) + { + for (var i = 0; i < list.Length; i++) + { + if (list[i] is T item) + { + items.Add(item); + } + } + } + + return items.ToImmutable(); + } + } +} diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalysisBuilder.cs b/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalysisBuilder.cs new file mode 100644 index 0000000000..e5b1217dd7 --- /dev/null +++ b/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalysisBuilder.cs @@ -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.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; + +namespace Microsoft.AspNetCore.Analyzers +{ + internal class StartupAnalysisBuilder + { + private readonly Dictionary> _analysesByType; + private readonly StartupAnalzyer _analyzer; + private readonly object _lock; + + public StartupAnalysisBuilder(StartupAnalzyer analyzer, StartupSymbols startupSymbols) + { + _analyzer = analyzer; + StartupSymbols = startupSymbols; + + _analysesByType = new Dictionary>(); + _lock = new object(); + } + + public StartupSymbols StartupSymbols { get; } + + public StartupAnalysis Build() + { + lock (_lock) + { + return new StartupAnalysis( + StartupSymbols, + _analysesByType.ToImmutableDictionary( + k => k.Key, + v => v.Value.ToImmutableArray())); + } + } + + public void ReportAnalysis(ServicesAnalysis analysis) + { + ReportAnalysisCore(analysis.StartupType, analysis); + _analyzer.OnServicesAnalysisCompleted(analysis); + } + + public void ReportAnalysis(OptionsAnalysis analysis) + { + ReportAnalysisCore(analysis.StartupType, analysis); + _analyzer.OnOptionsAnalysisCompleted(analysis); + } + + public void ReportAnalysis(MiddlewareAnalysis analysis) + { + ReportAnalysisCore(analysis.StartupType, analysis); + _analyzer.OnMiddlewareAnalysisCompleted(analysis); + } + + private void ReportAnalysisCore(INamedTypeSymbol type, object analysis) + { + lock (_lock) + { + if (!_analysesByType.TryGetValue(type, out var list)) + { + list = new List(); + _analysesByType.Add(type, list); + } + + list.Add(analysis); + } + } + } +} diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalysisContext.cs b/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalysisContext.cs deleted file mode 100644 index 12dc164ba9..0000000000 --- a/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalysisContext.cs +++ /dev/null @@ -1,22 +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.Diagnostics; - -namespace Microsoft.AspNetCore.Analyzers -{ - internal class StartupAnalysisContext - { -#pragma warning disable RS1012 // Start action has no registered actions. - public StartupAnalysisContext(OperationBlockStartAnalysisContext operationBlockStartAnalysisContext, StartupSymbols startupSymbols) - { - OperationBlockStartAnalysisContext = operationBlockStartAnalysisContext; - StartupSymbols = startupSymbols; - } - - public OperationBlockStartAnalysisContext OperationBlockStartAnalysisContext { get; } - - public StartupSymbols StartupSymbols { get; } -#pragma warning restore RS1012 // Start action has no registered actions. - } -} diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalyzer.Diagnostics.cs b/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalyzer.Diagnostics.cs index 1a3432ff42..0a04924177 100644 --- a/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalyzer.Diagnostics.cs +++ b/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalyzer.Diagnostics.cs @@ -1,6 +1,7 @@ // 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; @@ -8,23 +9,39 @@ 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.EnableEndpointRouting = false' inside '{1}'.", - "Usage", - DiagnosticSeverity.Warning, - isEnabledByDefault: true, - helpLinkUri: "https://aka.ms/YJggeFn"); + internal static class Diagnostics + { + public static readonly ImmutableArray SupportedDiagnostics; - internal readonly static DiagnosticDescriptor BuildServiceProviderShouldNotCalledInConfigureServicesMethod = new DiagnosticDescriptor( - "MVC1007", - "Do not call 'IServiceCollection.BuildServiceProvider' in 'ConfigureServices'", - "Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'.", - "Design", - DiagnosticSeverity.Warning, - isEnabledByDefault: true, - helpLinkUri: "https://aka.ms/AA5k895" - ); + static Diagnostics() + { + SupportedDiagnostics = ImmutableArray.Create(new[] + { + // ASP + BuildServiceProviderShouldNotCalledInConfigureServicesMethod, + + // MVC + UnsupportedUseMvcWithEndpointRouting, + }); + } + + internal readonly static DiagnosticDescriptor BuildServiceProviderShouldNotCalledInConfigureServicesMethod = new DiagnosticDescriptor( + "ASP0000", + "Do not call 'IServiceCollection.BuildServiceProvider' in 'ConfigureServices'", + "Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'.", + "Design", + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + helpLinkUri: "https://aka.ms/AA5k895"); + + 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"); + } } } diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalyzer.Events.cs b/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalyzer.Events.cs index 0a8c0dcde5..9e2a554948 100644 --- a/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalyzer.Events.cs +++ b/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalyzer.Events.cs @@ -7,27 +7,42 @@ using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.AspNetCore.Analyzers { + // Events for testability. Allows us to unit test the data we gather from analysis. public partial class StartupAnalzyer : DiagnosticAnalyzer { - internal event EventHandler? AnalysisStarted; - - private void OnAnalysisStarted(StartupComputedAnalysis analysis) - { - AnalysisStarted?.Invoke(this, analysis); - } - internal event EventHandler? ConfigureServicesMethodFound; - private void OnConfigureServicesMethodFound(IMethodSymbol method) + internal void OnConfigureServicesMethodFound(IMethodSymbol method) { ConfigureServicesMethodFound?.Invoke(this, method); } + internal event EventHandler? ServicesAnalysisCompleted; + + internal void OnServicesAnalysisCompleted(ServicesAnalysis analysis) + { + ServicesAnalysisCompleted?.Invoke(this, analysis); + } + + internal event EventHandler? OptionsAnalysisCompleted; + + internal void OnOptionsAnalysisCompleted(OptionsAnalysis analysis) + { + OptionsAnalysisCompleted?.Invoke(this, analysis); + } + internal event EventHandler? ConfigureMethodFound; - private void OnConfigureMethodFound(IMethodSymbol method) + internal void OnConfigureMethodFound(IMethodSymbol method) { ConfigureMethodFound?.Invoke(this, method); } + + internal event EventHandler? MiddlewareAnalysisCompleted; + + internal void OnMiddlewareAnalysisCompleted(MiddlewareAnalysis analysis) + { + MiddlewareAnalysisCompleted?.Invoke(this, analysis); + } } } diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalzyer.cs b/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalzyer.cs index 17c6bb7a74..3cbb83c180 100644 --- a/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalzyer.cs +++ b/src/Mvc/Mvc.Analyzers/src/Startup/StartupAnalzyer.cs @@ -2,7 +2,6 @@ // 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; @@ -12,46 +11,16 @@ 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[] ConfigureServicesMethodAnalysisFactories = new Func[] - { - ServicesAnalysis.CreateAndInitialize, - MvcOptionsAnalysis.CreateAndInitialize, - }; - - private readonly static Func[] ConfigureMethodAnalysisFactories = new Func[] - { - MiddlewareAnalysis.CreateAndInitialize, - }; - - private readonly static Func, StartupDiagnosticValidator>[] DiagnosticValidatorFactories = new Func, StartupDiagnosticValidator>[] - { - UseMvcDiagnosticValidator.CreateAndInitialize, - BuildServiceProviderValidator.CreateAndInitialize - }; - -#pragma warning restore RS1008 // Avoid storing per-compilation data into the fields of a diagnostic analyzer. - - public StartupAnalzyer() - { - SupportedDiagnostics = ImmutableArray.Create(new[] - { - UnsupportedUseMvcWithEndpointRouting, - BuildServiceProviderShouldNotCalledInConfigureServicesMethod - }); - - // 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 StartupFilePredicate { get; set; } - - public override ImmutableArray SupportedDiagnostics { get; } + public override ImmutableArray SupportedDiagnostics => Diagnostics.SupportedDiagnostics; public override void Initialize(AnalysisContext context) { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.RegisterCompilationStartAction(OnCompilationStart); } @@ -61,91 +30,61 @@ namespace Microsoft.AspNetCore.Analyzers 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) + if (!symbols.HasRequiredSymbols) { 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(); - - context.RegisterOperationBlockStartAction(context => + context.RegisterSymbolStartAction(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 analyses) - { - for (var i = 0; i < DiagnosticValidatorFactories.Length; i++) - { - var validator = DiagnosticValidatorFactories[i].Invoke(analysisContext, analyses); - } - } - - private void AnalyzeStartupMethods(OperationBlockStartAnalysisContext context, StartupSymbols symbols, ConcurrentBag 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 type = (INamedTypeSymbol)context.Symbol; + if (!StartupFacts.IsStartupClass(symbols, type)) { - var analysis = ConfigureServicesMethodAnalysisFactories[i].Invoke(startupAnalysisContext); - analyses.Add(analysis); - - OnAnalysisStarted(analysis); + // Not a startup class, nothing to do. + return; } - OnConfigureServicesMethodFound(method); - } + // This analyzer fans out a bunch of jobs. The context will capture the results of doing analysis + // on the startup code, so that other analyzers that run later can examine them. + var builder = new StartupAnalysisBuilder(this, symbols); - if (StartupFacts.IsConfigure(symbols, method)) - { - for (var i = 0; i < ConfigureMethodAnalysisFactories.Length; i++) + var services = new ServicesAnalyzer(builder); + var options = new OptionsAnalyzer(builder); + var middleware = new MiddlewareAnalyzer(builder); + + context.RegisterOperationBlockStartAction(context => { - var analysis = ConfigureMethodAnalysisFactories[i].Invoke(startupAnalysisContext); - analyses.Add(analysis); + if (context.OwningSymbol.Kind != SymbolKind.Method) + { + return; + } - OnAnalysisStarted(analysis); - } + var method = (IMethodSymbol)context.OwningSymbol; + if (StartupFacts.IsConfigureServices(symbols, method)) + { + OnConfigureServicesMethodFound(method); - OnConfigureMethodFound(method); - } - } -#pragma warning disable RS1012 // Start action has no registered actions. - private bool IsStartupFile(OperationBlockStartAnalysisContext context) - { - foreach (var location in context.OwningSymbol.Locations) - { - if (location.IsInSource && StartupFilePredicate(location.SourceTree.FilePath)) + services.AnalyzeConfigureServices(context); + options.AnalyzeConfigureServices(context); + } + + if (StartupFacts.IsConfigure(symbols, method)) + { + OnConfigureMethodFound(method); + + middleware.AnalyzeConfigureMethod(context); + } + }); + + // Run after analyses have had a chance to finish to add diagnostics. + context.RegisterSymbolEndAction(context => { - return true; - } - } + var analysis = builder.Build(); + new UseMvcAnalyzer(analysis).AnalyzeSymbol(context); + new BuildServiceProviderValidator(analysis).AnalyzeSymbol(context); + }); - return false; + }, SymbolKind.NamedType); } -#pragma warning restore RS1012 // Start action has no registered actions. } } diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/StartupComputedAnalysis.cs b/src/Mvc/Mvc.Analyzers/src/Startup/StartupComputedAnalysis.cs deleted file mode 100644 index 0bd9efb687..0000000000 --- a/src/Mvc/Mvc.Analyzers/src/Startup/StartupComputedAnalysis.cs +++ /dev/null @@ -1,17 +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.Analyzers -{ - internal abstract class StartupComputedAnalysis - { - protected StartupComputedAnalysis(INamedTypeSymbol enclosingType) - { - EnclosingType = enclosingType; - } - - public INamedTypeSymbol EnclosingType { get; } - } -} diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/StartupDiagnosticValidator.cs b/src/Mvc/Mvc.Analyzers/src/Startup/StartupDiagnosticValidator.cs deleted file mode 100644 index 56e6dcda7e..0000000000 --- a/src/Mvc/Mvc.Analyzers/src/Startup/StartupDiagnosticValidator.cs +++ /dev/null @@ -1,9 +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.Analyzers -{ - internal abstract class StartupDiagnosticValidator - { - } -} diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/StartupFacts.cs b/src/Mvc/Mvc.Analyzers/src/Startup/StartupFacts.cs index 4fc93ad6b6..ba13dc63be 100644 --- a/src/Mvc/Mvc.Analyzers/src/Startup/StartupFacts.cs +++ b/src/Mvc/Mvc.Analyzers/src/Startup/StartupFacts.cs @@ -2,13 +2,41 @@ // 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 IsStartupClass(StartupSymbols symbols, INamedTypeSymbol type) + { + if (symbols == null) + { + throw new ArgumentNullException(nameof(symbols)); + } + + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + // It's not good enough to just look for ConfigureServices or Configure as a hueristic. + // ConfigureServices might not appear in trivial cases, and Configure might be named ConfigureDevelopment + // or something similar. + // + // Since we already are analyzing the symbol it should be cheap to do a quick pass over the members. + var members = type.GetMembers(); + for (var i = 0; i < members.Length; i++) + { + if (members[i] is IMethodSymbol method && (IsConfigureServices(symbols, method) || IsConfigure(symbols, method))) + { + return true; + } + } + + return false; + } + public static bool IsConfigureServices(StartupSymbols symbols, IMethodSymbol symbol) { if (symbol == null) @@ -21,7 +49,7 @@ namespace Microsoft.AspNetCore.Analyzers return false; } - if (!string.Equals(symbol.Name, SymbolNames.ConfigureServicesMethod, StringComparison.Ordinal)) + if (!string.Equals(symbol.Name, "ConfigureServices", StringComparison.Ordinal)) { return false; } @@ -51,7 +79,7 @@ namespace Microsoft.AspNetCore.Analyzers return false; } - if (symbol.Name == null || !symbol.Name.StartsWith(SymbolNames.ConfigureMethod, StringComparison.Ordinal)) + if (symbol.Name == null || !symbol.Name.StartsWith("Configure", StringComparison.Ordinal)) { return false; } @@ -59,7 +87,7 @@ namespace Microsoft.AspNetCore.Analyzers // IApplicationBuilder can appear in any parameter for (var i = 0; i < symbol.Parameters.Length; i++) { - if (symbol.Parameters[i].Type == symbols.IApplicationBuilder) + if (symbol.Parameters[i].Type == symbols.IApplicationBuilder) { return true; } diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/StartupSymbols.cs b/src/Mvc/Mvc.Analyzers/src/Startup/StartupSymbols.cs index 5595cd1a6d..f528c9fa48 100644 --- a/src/Mvc/Mvc.Analyzers/src/Startup/StartupSymbols.cs +++ b/src/Mvc/Mvc.Analyzers/src/Startup/StartupSymbols.cs @@ -1,7 +1,6 @@ // 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 @@ -10,15 +9,15 @@ namespace Microsoft.AspNetCore.Analyzers { public StartupSymbols(Compilation compilation) { - IApplicationBuilder = compilation.GetTypeByMetadataName(SymbolNames.IApplicationBuilder); - IServiceCollection = compilation.GetTypeByMetadataName(SymbolNames.IServiceCollection); - MvcOptions = compilation.GetTypeByMetadataName(SymbolNames.MvcOptions); + IApplicationBuilder = compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Builder.IApplicationBuilder"); + IServiceCollection = compilation.GetTypeByMetadataName("Microsoft.Extensions.DependencyInjection.IServiceCollection"); + MvcOptions = compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Mvc.MvcOptions"); } + public bool HasRequiredSymbols => IApplicationBuilder != null && IServiceCollection != null; + public INamedTypeSymbol IApplicationBuilder { get; } - public INamedTypeSymbol IServiceCollection { get; } - public INamedTypeSymbol MvcOptions { get; } } } diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/UseMvcAnalyzer.cs b/src/Mvc/Mvc.Analyzers/src/Startup/UseMvcAnalyzer.cs new file mode 100644 index 0000000000..4cbb702ad1 --- /dev/null +++ b/src/Mvc/Mvc.Analyzers/src/Startup/UseMvcAnalyzer.cs @@ -0,0 +1,55 @@ +// 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; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.AspNetCore.Analyzers +{ + internal class UseMvcAnalyzer + { + private readonly StartupAnalysis _context; + + public UseMvcAnalyzer(StartupAnalysis context) + { + _context = context; + } + + public void AnalyzeSymbol(SymbolAnalysisContext context) + { + Debug.Assert(context.Symbol.Kind == SymbolKind.NamedType); + Debug.Assert(StartupFacts.IsStartupClass(_context.StartupSymbols, (INamedTypeSymbol)context.Symbol)); + + var type = (INamedTypeSymbol)context.Symbol; + + var optionsAnalysis = _context.GetRelatedSingletonAnalysis(type); + if (optionsAnalysis == null) + { + return; + } + + // 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 middlewareAnalysis in _context.GetRelatedAnalyses(type)) + { + foreach (var middlewareItem in middlewareAnalysis.Middleware) + { + if (middlewareItem.UseMethod.Name == "UseMvc" || middlewareItem.UseMethod.Name == "UseMvcWithDefaultRoute") + { + // Report a diagnostic if it's unclear that the user turned off Endpoint Routing. + if (!OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis)) + { + context.ReportDiagnostic(Diagnostic.Create( + StartupAnalzyer.Diagnostics.UnsupportedUseMvcWithEndpointRouting, + middlewareItem.Operation.Syntax.GetLocation(), + middlewareItem.UseMethod.Name, + optionsAnalysis.ConfigureServicesMethod.Name)); + } + } + } + } + } + } +} diff --git a/src/Mvc/Mvc.Analyzers/src/Startup/UseMvcDiagnosticValidator.cs b/src/Mvc/Mvc.Analyzers/src/Startup/UseMvcDiagnosticValidator.cs deleted file mode 100644 index 96464f274e..0000000000 --- a/src/Mvc/Mvc.Analyzers/src/Startup/UseMvcDiagnosticValidator.cs +++ /dev/null @@ -1,51 +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.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 analyses) - { - if (analyses == null) - { - throw new ArgumentNullException(nameof(analyses)); - } - - var validator = new UseMvcDiagnosticValidator(); - - foreach (var mvcOptionsAnalysis in analyses.OfType()) - { - // 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().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; - } - } -} diff --git a/src/Mvc/Mvc.Analyzers/test/StartupAnalyzerTest.cs b/src/Mvc/Mvc.Analyzers/test/StartupAnalyzerTest.cs index 16c7af384d..ef76ab0ff0 100644 --- a/src/Mvc/Mvc.Analyzers/test/StartupAnalyzerTest.cs +++ b/src/Mvc/Mvc.Analyzers/test/StartupAnalyzerTest.cs @@ -17,14 +17,15 @@ namespace Microsoft.AspNetCore.Analyzers public StartupAnalyzerTest() { StartupAnalyzer = new StartupAnalzyer(); - StartupAnalyzer.StartupFilePredicate = path => path.Equals("Test.cs", StringComparison.Ordinal); Runner = new MvcDiagnosticAnalyzerRunner(StartupAnalyzer); - Analyses = new ConcurrentBag(); + Analyses = new ConcurrentBag(); ConfigureServicesMethods = new ConcurrentBag(); ConfigureMethods = new ConcurrentBag(); - StartupAnalyzer.AnalysisStarted += (sender, analysis) => Analyses.Add(analysis); + StartupAnalyzer.ServicesAnalysisCompleted += (sender, analysis) => Analyses.Add(analysis); + StartupAnalyzer.OptionsAnalysisCompleted += (sender, analysis) => Analyses.Add(analysis); + StartupAnalyzer.MiddlewareAnalysisCompleted += (sender, analysis) => Analyses.Add(analysis); StartupAnalyzer.ConfigureServicesMethodFound += (sender, method) => ConfigureServicesMethods.Add(method); StartupAnalyzer.ConfigureMethodFound += (sender, method) => ConfigureMethods.Add(method); } @@ -33,7 +34,7 @@ namespace Microsoft.AspNetCore.Analyzers private MvcDiagnosticAnalyzerRunner Runner { get; } - private ConcurrentBag Analyses { get; } + private ConcurrentBag Analyses { get; } private ConcurrentBag ConfigureServicesMethods { get; } @@ -87,8 +88,8 @@ namespace Microsoft.AspNetCore.Analyzers var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); // Assert - var mvcOptionsAnalysis = Assert.Single(Analyses.OfType()); - Assert.False(mvcOptionsAnalysis.EndpointRoutingEnabled); + var optionsAnalysis = Assert.Single(Analyses.OfType()); + Assert.True(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis)); var middlewareAnalysis = Assert.Single(Analyses.OfType()); var middleware = Assert.Single(middlewareAnalysis.Middleware); @@ -107,8 +108,8 @@ namespace Microsoft.AspNetCore.Analyzers var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); // Assert - var mvcOptionsAnalysis = Assert.Single(Analyses.OfType()); - Assert.False(mvcOptionsAnalysis.EndpointRoutingEnabled); + var optionsAnalysis = Assert.Single(Analyses.OfType()); + Assert.True(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis)); var middlewareAnalysis = Assert.Single(Analyses.OfType()); var middleware = Assert.Single(middlewareAnalysis.Middleware); @@ -130,8 +131,8 @@ namespace Microsoft.AspNetCore.Analyzers var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); // Assert - var mvcOptionsAnalysis = Assert.Single(Analyses.OfType()); - Assert.Null(mvcOptionsAnalysis.EndpointRoutingEnabled); + var optionsAnalysis = Assert.Single(Analyses.OfType()); + Assert.False(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis)); var middlewareAnalysis = Assert.Single(Analyses.OfType()); var middleware = Assert.Single(middlewareAnalysis.Middleware); @@ -141,7 +142,7 @@ namespace Microsoft.AspNetCore.Analyzers diagnostics, diagnostic => { - Assert.Same(StartupAnalzyer.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); + Assert.Same(StartupAnalzyer.Diagnostics.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); }); } @@ -156,8 +157,8 @@ namespace Microsoft.AspNetCore.Analyzers var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); // Assert - var mvcOptionsAnalysis = Assert.Single(Analyses.OfType()); - Assert.Null(mvcOptionsAnalysis.EndpointRoutingEnabled); + var optionsAnalysis = Assert.Single(Analyses.OfType()); + Assert.False(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis)); var middlewareAnalysis = Assert.Single(Analyses.OfType()); @@ -173,7 +174,7 @@ namespace Microsoft.AspNetCore.Analyzers diagnostics, diagnostic => { - Assert.Same(StartupAnalzyer.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); + Assert.Same(StartupAnalzyer.Diagnostics.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); }); } @@ -188,27 +189,28 @@ namespace Microsoft.AspNetCore.Analyzers var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); // Assert - var mvcOptionsAnalysis = Assert.Single(Analyses.OfType()); - Assert.Null(mvcOptionsAnalysis.EndpointRoutingEnabled); + var optionsAnalysis = Assert.Single(Analyses.OfType()); + Assert.False(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis)); Assert.Collection( diagnostics, diagnostic => { - Assert.Same(StartupAnalzyer.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); + Assert.Same(StartupAnalzyer.Diagnostics.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM1"], diagnostic.Location); }, diagnostic => { - Assert.Same(StartupAnalzyer.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); + Assert.Same(StartupAnalzyer.Diagnostics.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM2"], diagnostic.Location); }, diagnostic => { - Assert.Same(StartupAnalzyer.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); + Assert.Same(StartupAnalzyer.Diagnostics.UnsupportedUseMvcWithEndpointRouting, diagnostic.Descriptor); AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM3"], diagnostic.Location); }); } + [Fact] public async Task StartupAnalyzer_ServicesAnalysis_CallBuildServiceProvider() { @@ -224,10 +226,11 @@ namespace Microsoft.AspNetCore.Analyzers Assert.Collection(diagnostics, diagnostic => { - Assert.Same(StartupAnalzyer.BuildServiceProviderShouldNotCalledInConfigureServicesMethod, diagnostic.Descriptor); + Assert.Same(StartupAnalzyer.Diagnostics.BuildServiceProviderShouldNotCalledInConfigureServicesMethod, diagnostic.Descriptor); AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM1"], diagnostic.Location); }); } + private TestSource ReadSource(string fileName) { return MvcTestSource.Read(nameof(StartupAnalyzerTest), fileName); diff --git a/src/Mvc/Mvc.Api.Analyzers/src/AddResponseTypeAttributeCodeFixAction.cs b/src/Mvc/Mvc.Api.Analyzers/src/AddResponseTypeAttributeCodeFixAction.cs index 0e23036cce..1f4c91bfad 100644 --- a/src/Mvc/Mvc.Api.Analyzers/src/AddResponseTypeAttributeCodeFixAction.cs +++ b/src/Mvc/Mvc.Api.Analyzers/src/AddResponseTypeAttributeCodeFixAction.cs @@ -205,7 +205,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers SyntaxFactory.ParseName(ApiSymbolNames.ProducesResponseTypeAttribute) .WithAdditionalAnnotations(Simplifier.Annotation), SyntaxFactory.AttributeArgumentList().AddArguments( - + SyntaxFactory.AttributeArgument(statusCodeSyntax))); }