|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
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 a method called ConfigureServices or Configure as a hueristic.
// ConfigureServices might not appear in trivial cases, and Configure might be named ConfigureDevelopment
// or something similar.
//
// Additionally, a startup class could be called anything and wired up explicitly.
//
// Since we already are analyzing the symbol it should be cheap to do a 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;
}
// Based on StartupLoader. The philosophy is that we want to do analysis only on things
// that would be recognized as a ConfigureServices method to avoid false positives.
//
// The ConfigureServices method follows the naming pattern `Configure{Environment?}Services` (ignoring case).
// The ConfigureServices method must be public.
// The ConfigureServices method can be instance or static.
// The ConfigureServices method cannot have other parameters besides IServiceCollection.
//
// The ConfigureServices method does not actually need to accept IServiceCollection
// but we exclude that case because a ConfigureServices method that doesn't accept an
// IServiceCollection can't do anything interesting to analysis.
public static bool IsConfigureServices(StartupSymbols symbols, IMethodSymbol symbol)
{
if (symbol == null)
{
throw new ArgumentNullException(nameof(symbol));
}
if (symbol.DeclaredAccessibility != Accessibility.Public)
{
return false;
}
if (symbol.Name == null ||
!symbol.Name.StartsWith(SymbolNames.ConfigureServicesMethodPrefix, StringComparison.OrdinalIgnoreCase) ||
!symbol.Name.EndsWith(SymbolNames.ConfigureServicesMethodSuffix, StringComparison.OrdinalIgnoreCase))
{
return false;
}
if (symbol.Parameters.Length != 1)
{
return false;
}
return SymbolEqualityComparer.Default.Equals(symbol.Parameters[0].Type, symbols.IServiceCollection);
}
// Based on StartupLoader. The philosophy is that we want to do analysis only on things
// that would be recognized as a Configure method to avoid false positives.
//
// The Configure method follows the naming pattern `Configure{Environment?}` (ignoring case).
// The Configure method must be public.
// The Configure method can be instance or static.
// The Configure method *can* have other parameters besides IApplicationBuilder.
//
// The Configure method does not actually need to accept IApplicationBuilder
// but we exclude that case because a Configure method that doesn't accept an
// IApplicationBuilder can't do anything interesting to analysis.
public static bool IsConfigure(StartupSymbols symbols, IMethodSymbol symbol)
{
if (symbol == null)
{
throw new ArgumentNullException(nameof(symbol));
}
if (symbol.DeclaredAccessibility != Accessibility.Public)
{
return false;
}
if (symbol.Name == null ||
!symbol.Name.StartsWith(SymbolNames.ConfigureMethodPrefix, StringComparison.OrdinalIgnoreCase))
{
return false;
}
// IApplicationBuilder can appear in any parameter, but must appear.
for (var i = 0; i < symbol.Parameters.Length; i++)
{
if (SymbolEqualityComparer.Default.Equals(symbol.Parameters[i].Type, symbols.IApplicationBuilder))
{
return true;
}
}
return false;
}
// Based on the three known gestures for including SignalR in a ConfigureMethod:
// UseSignalR() // middleware
// MapHub<>() // endpoint routing
// MapBlazorHub() // server-side blazor
//
// To be slightly less brittle, we don't look at the exact symbols and instead just look
// at method names in here. We're NOT worried about false negatives, because all of these
// cases contain words like SignalR or Hub.
public static bool IsSignalRConfigureMethodGesture(IMethodSymbol symbol)
{
if (symbol == null)
{
throw new ArgumentNullException(nameof(symbol));
}
// UseSignalR has been removed in 5.0, but we should probably still check for it in this analyzer in case the user
// installs it into a pre-5.0 app.
if (string.Equals(symbol.Name, SymbolNames.SignalRAppBuilderExtensions.UseSignalRMethodName, StringComparison.Ordinal) ||
string.Equals(symbol.Name, SymbolNames.HubEndpointRouteBuilderExtensions.MapHubMethodName, StringComparison.Ordinal) ||
string.Equals(symbol.Name, SymbolNames.ComponentEndpointRouteBuilderExtensions.MapBlazorHubMethodName, StringComparison.Ordinal))
{
return true;
}
return false;
}
}
|