Refactor of Startup analyzer code
This commit is contained in:
parent
d98bde4fc7
commit
753e98c96d
|
|
@ -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<StartupComputedAnalysis> 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<ServicesAnalysis>())
|
||||
var type = (INamedTypeSymbol)context.Symbol;
|
||||
|
||||
foreach (var serviceAnalysis in _context.GetRelatedAnalyses<ServicesAnalysis>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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<MiddlewareItem> 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<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;
|
||||
ConfigureMethod = configureMethod;
|
||||
Middleware = middleware;
|
||||
}
|
||||
|
||||
public MiddlewareAnalysis(IMethodSymbol configureMethod)
|
||||
: base(configureMethod)
|
||||
{
|
||||
}
|
||||
public INamedTypeSymbol StartupType => ConfigureMethod.ContainingType;
|
||||
|
||||
public ImmutableArray<MiddlewareItem> Middleware { get; private set; } = ImmutableArray<MiddlewareItem>.Empty;
|
||||
public IMethodSymbol ConfigureMethod { get; }
|
||||
|
||||
public ImmutableArray<MiddlewareItem> Middleware { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<MiddlewareItem>();
|
||||
|
||||
// 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()));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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<OptionsItem> options)
|
||||
{
|
||||
ConfigureServicesMethod = configureServicesMethod;
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public INamedTypeSymbol StartupType => ConfigureServicesMethod.ContainingType;
|
||||
|
||||
public IMethodSymbol ConfigureServicesMethod { get; }
|
||||
|
||||
public ImmutableArray<OptionsItem> Options { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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<OptionsItem>();
|
||||
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()));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ServicesItem> 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<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;
|
||||
ConfigureServicesMethod = configureServicesMethod;
|
||||
Services = services;
|
||||
}
|
||||
|
||||
public ServicesAnalysis(IMethodSymbol configureServicesMethod)
|
||||
: base(configureServicesMethod)
|
||||
{
|
||||
}
|
||||
public INamedTypeSymbol StartupType => ConfigureServicesMethod.ContainingType;
|
||||
|
||||
public ImmutableArray<ServicesItem> Services { get; private set; } = ImmutableArray<ServicesItem>.Empty;
|
||||
public IMethodSymbol ConfigureServicesMethod { get; }
|
||||
|
||||
public ImmutableArray<ServicesItem> Services { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ServicesItem>();
|
||||
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()));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<INamedTypeSymbol, ImmutableArray<object>> _analysesByType;
|
||||
|
||||
public StartupAnalysis(
|
||||
StartupSymbols startupSymbols,
|
||||
ImmutableDictionary<INamedTypeSymbol, ImmutableArray<object>> analysesByType)
|
||||
{
|
||||
StartupSymbols = startupSymbols;
|
||||
_analysesByType = analysesByType;
|
||||
}
|
||||
|
||||
public StartupSymbols StartupSymbols { get; }
|
||||
|
||||
public T? GetRelatedSingletonAnalysis<T>(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<T> GetRelatedAnalyses<T>(INamedTypeSymbol type) where T : class
|
||||
{
|
||||
var items = ImmutableArray.CreateBuilder<T>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<INamedTypeSymbol, List<object>> _analysesByType;
|
||||
private readonly StartupAnalzyer _analyzer;
|
||||
private readonly object _lock;
|
||||
|
||||
public StartupAnalysisBuilder(StartupAnalzyer analyzer, StartupSymbols startupSymbols)
|
||||
{
|
||||
_analyzer = analyzer;
|
||||
StartupSymbols = startupSymbols;
|
||||
|
||||
_analysesByType = new Dictionary<INamedTypeSymbol, List<object>>();
|
||||
_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<object>();
|
||||
_analysesByType.Add(type, list);
|
||||
}
|
||||
|
||||
list.Add(analysis);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
|
@ -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<DiagnosticDescriptor> 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<DiagnosticDescriptor>(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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<StartupComputedAnalysis>? AnalysisStarted;
|
||||
|
||||
private void OnAnalysisStarted(StartupComputedAnalysis analysis)
|
||||
{
|
||||
AnalysisStarted?.Invoke(this, analysis);
|
||||
}
|
||||
|
||||
internal event EventHandler<IMethodSymbol>? ConfigureServicesMethodFound;
|
||||
|
||||
private void OnConfigureServicesMethodFound(IMethodSymbol method)
|
||||
internal void OnConfigureServicesMethodFound(IMethodSymbol method)
|
||||
{
|
||||
ConfigureServicesMethodFound?.Invoke(this, method);
|
||||
}
|
||||
|
||||
internal event EventHandler<ServicesAnalysis>? ServicesAnalysisCompleted;
|
||||
|
||||
internal void OnServicesAnalysisCompleted(ServicesAnalysis analysis)
|
||||
{
|
||||
ServicesAnalysisCompleted?.Invoke(this, analysis);
|
||||
}
|
||||
|
||||
internal event EventHandler<OptionsAnalysis>? OptionsAnalysisCompleted;
|
||||
|
||||
internal void OnOptionsAnalysisCompleted(OptionsAnalysis analysis)
|
||||
{
|
||||
OptionsAnalysisCompleted?.Invoke(this, analysis);
|
||||
}
|
||||
|
||||
internal event EventHandler<IMethodSymbol>? ConfigureMethodFound;
|
||||
|
||||
private void OnConfigureMethodFound(IMethodSymbol method)
|
||||
internal void OnConfigureMethodFound(IMethodSymbol method)
|
||||
{
|
||||
ConfigureMethodFound?.Invoke(this, method);
|
||||
}
|
||||
|
||||
internal event EventHandler<MiddlewareAnalysis>? MiddlewareAnalysisCompleted;
|
||||
|
||||
internal void OnMiddlewareAnalysisCompleted(MiddlewareAnalysis analysis)
|
||||
{
|
||||
MiddlewareAnalysisCompleted?.Invoke(this, analysis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<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,
|
||||
BuildServiceProviderValidator.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,
|
||||
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<string, bool> StartupFilePredicate { get; set; }
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
|
||||
public override ImmutableArray<DiagnosticDescriptor> 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<StartupComputedAnalysis>();
|
||||
|
||||
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<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 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.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<OptionsAnalysis>(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<MiddlewareAnalysis>(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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<StartupComputedAnalysis>();
|
||||
Analyses = new ConcurrentBag<object>();
|
||||
ConfigureServicesMethods = new ConcurrentBag<IMethodSymbol>();
|
||||
ConfigureMethods = new ConcurrentBag<IMethodSymbol>();
|
||||
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<StartupComputedAnalysis> Analyses { get; }
|
||||
private ConcurrentBag<object> Analyses { get; }
|
||||
|
||||
private ConcurrentBag<IMethodSymbol> ConfigureServicesMethods { get; }
|
||||
|
||||
|
|
@ -87,8 +88,8 @@ namespace Microsoft.AspNetCore.Analyzers
|
|||
var diagnostics = await Runner.GetDiagnosticsAsync(source.Source);
|
||||
|
||||
// Assert
|
||||
var mvcOptionsAnalysis = Assert.Single(Analyses.OfType<MvcOptionsAnalysis>());
|
||||
Assert.False(mvcOptionsAnalysis.EndpointRoutingEnabled);
|
||||
var optionsAnalysis = Assert.Single(Analyses.OfType<OptionsAnalysis>());
|
||||
Assert.True(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis));
|
||||
|
||||
var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>());
|
||||
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<MvcOptionsAnalysis>());
|
||||
Assert.False(mvcOptionsAnalysis.EndpointRoutingEnabled);
|
||||
var optionsAnalysis = Assert.Single(Analyses.OfType<OptionsAnalysis>());
|
||||
Assert.True(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis));
|
||||
|
||||
var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>());
|
||||
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<MvcOptionsAnalysis>());
|
||||
Assert.Null(mvcOptionsAnalysis.EndpointRoutingEnabled);
|
||||
var optionsAnalysis = Assert.Single(Analyses.OfType<OptionsAnalysis>());
|
||||
Assert.False(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis));
|
||||
|
||||
var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>());
|
||||
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<MvcOptionsAnalysis>());
|
||||
Assert.Null(mvcOptionsAnalysis.EndpointRoutingEnabled);
|
||||
var optionsAnalysis = Assert.Single(Analyses.OfType<OptionsAnalysis>());
|
||||
Assert.False(OptionsFacts.IsEndpointRoutingExplicitlyDisabled(optionsAnalysis));
|
||||
|
||||
var middlewareAnalysis = Assert.Single(Analyses.OfType<MiddlewareAnalysis>());
|
||||
|
||||
|
|
@ -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<MvcOptionsAnalysis>());
|
||||
Assert.Null(mvcOptionsAnalysis.EndpointRoutingEnabled);
|
||||
var optionsAnalysis = Assert.Single(Analyses.OfType<OptionsAnalysis>());
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers
|
|||
SyntaxFactory.ParseName(ApiSymbolNames.ProducesResponseTypeAttribute)
|
||||
.WithAdditionalAnnotations(Simplifier.Annotation),
|
||||
SyntaxFactory.AttributeArgumentList().AddArguments(
|
||||
|
||||
|
||||
SyntaxFactory.AttributeArgument(statusCodeSyntax)));
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue