// 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); }
public async Task DetectFeaturesAsync_FindsNoFeatures() { // Arrange var source = @" using Microsoft.AspNetCore.Builder; namespace Microsoft.AspNetCore.Analyzers.TestFiles.CompilationFeatureDetectorTest { public class StartupWithNoFeatures { public void Configure(IApplicationBuilder app) { app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapFallbackToFile(""index.html""); }); } } }"; var compilation = TestCompilation.Create(source); var symbols = new StartupSymbols(compilation); var type = (INamedTypeSymbol)compilation.GetSymbolsWithName("StartupWithNoFeatures").Single(); Assert.True(StartupFacts.IsStartupClass(symbols, type)); // Act var features = await CompilationFeatureDetector.DetectFeaturesAsync(compilation); // Assert Assert.Empty(features); }
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)); }
public static List <IMethodSymbol> FindConfigureMethods(StartupSymbols symbols, IAssemblySymbol assembly) { var visitor = new ConfigureMethodVisitor(symbols); visitor.Visit(assembly); return(visitor._methods); }
public StartupAnalysis( StartupSymbols startupSymbols, ImmutableDictionary <INamedTypeSymbol, ImmutableArray <object> > analysesByType) { StartupSymbols = startupSymbols; _analysesByType = analysesByType; }
public StartupAnalysisBuilder(StartupAnalyzer analyzer, StartupSymbols startupSymbols) { _analyzer = analyzer; StartupSymbols = startupSymbols; #pragma warning disable RS1024 // Compare symbols correctly _analysesByType = new Dictionary <INamedTypeSymbol, List <object> >(SymbolEqualityComparer.Default); #pragma warning restore RS1024 // Compare symbols correctly _lock = new object(); }
public void IsStartupClass_RejectsNotStartupClass(string source) { // Arrange var compilation = TestCompilation.Create(TestSources[source]); var symbols = new StartupSymbols(compilation); var type = (INamedTypeSymbol)compilation.GetSymbolsWithName(source).Single(); // Act var result = StartupFacts.IsStartupClass(symbols, type); // Assert Assert.False(result); }
public void IsConfigure_RejectsNonConfigureMethod(string source, string methodName) { // Arrange var compilation = TestCompilation.Create(TestSources[source]); var symbols = new StartupSymbols(compilation); var type = (INamedTypeSymbol)compilation.GetSymbolsWithName(source).Single(); var methods = type.GetMembers(methodName).Cast <IMethodSymbol>(); foreach (var method in methods) { // Act var result = StartupFacts.IsConfigure(symbols, method); // Assert Assert.False(result); } }
public static async Task <IImmutableSet <string> > DetectFeaturesAsync( Compilation compilation, CancellationToken cancellationToken = default) { var symbols = new StartupSymbols(compilation); if (!symbols.HasRequiredSymbols) { // Cannot find ASP.NET Core types. return(ImmutableHashSet <string> .Empty); } var features = ImmutableHashSet.CreateBuilder <string>(); // Find configure methods in the project's assembly var configureMethods = ConfigureMethodVisitor.FindConfigureMethods(symbols, compilation.Assembly); for (var i = 0; i < configureMethods.Count; i++) { var configureMethod = configureMethods[i]; // Handles the case where a method is using partial definitions. We don't expect this to occur, but still handle it correctly. var syntaxReferences = configureMethod.DeclaringSyntaxReferences; for (var j = 0; j < syntaxReferences.Length; j++) { var semanticModel = compilation.GetSemanticModel(syntaxReferences[j].SyntaxTree); var syntax = await syntaxReferences[j].GetSyntaxAsync(cancellationToken).ConfigureAwait(false); var operation = semanticModel.GetOperation(syntax, cancellationToken); // Look for a call to one of the SignalR gestures that applies to the Configure method. if (operation .Descendants() .OfType <IInvocationOperation>() .Any(op => StartupFacts.IsSignalRConfigureMethodGesture(op.TargetMethod))) { features.Add(WellKnownFeatures.SignalR); } } } return(features.ToImmutable()); }
public async Task DetectFeatureAsync_StartupWithMapHub_FindsSignalR() { var source = @" using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.SignalR; namespace Microsoft.AspNetCore.Analyzers.TestFiles.CompilationFeatureDetectorTest { public class StartupWithMapHub { public void Configure(IApplicationBuilder app) { app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapHub<MyHub>("" / test""); }); } } public class MyHub : Hub { } } "; var compilation = TestCompilation.Create(source); var symbols = new StartupSymbols(compilation); var type = (INamedTypeSymbol)compilation.GetSymbolsWithName("StartupWithMapHub").Single(); Assert.True(StartupFacts.IsStartupClass(symbols, type)); // Act var features = await CompilationFeatureDetector.DetectFeaturesAsync(compilation); // Assert Assert.Collection(features, f => Assert.Equal(WellKnownFeatures.SignalR, f)); }
private ConfigureMethodVisitor(StartupSymbols symbols) { _symbols = symbols; _methods = new List <IMethodSymbol>(); }
public void FindConfigureMethods_AtDifferentScopes() { // Arrange var source = @" using Microsoft.AspNetCore.Builder; public class GlobalStartup { public void Configure(IApplicationBuilder app) { } } namespace Another { public class AnotherStartup { public void Configure(IApplicationBuilder app) { } } } namespace ANamespace { public class Startup { public void ConfigureDevelopment(IApplicationBuilder app) { } public class NestedStartup { public void ConfigureTest(IApplicationBuilder app) { } } } } namespace ANamespace.Nested { public class Startup { public void Configure(IApplicationBuilder app) { } public class NestedStartup { public void Configure(IApplicationBuilder app) { } } } }"; var expected = new string[] { "global::ANamespace.Nested.Startup.Configure", "global::ANamespace.Nested.Startup.NestedStartup.Configure", "global::ANamespace.Startup.ConfigureDevelopment", "global::ANamespace.Startup.NestedStartup.ConfigureTest", "global::Another.AnotherStartup.Configure", "global::GlobalStartup.Configure", }; var compilation = TestCompilation.Create(source); var symbols = new StartupSymbols(compilation); // Act var results = ConfigureMethodVisitor.FindConfigureMethods(symbols, compilation.Assembly); // Assert var actual = results .Select(m => m.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + "." + m.Name) .OrderBy(s => s) .ToArray(); Assert.Equal(expected, actual); }
private void OnCompilationStart(CompilationStartAnalysisContext context) { var symbols = new StartupSymbols(context.Compilation); // Don't run analyzer if ASP.NET Core types cannot be found if (!symbols.HasRequiredSymbols) { return; } var entryPoint = context.Compilation.GetEntryPoint(context.CancellationToken); context.RegisterSymbolStartAction(context => { var type = (INamedTypeSymbol)context.Symbol; if (!StartupFacts.IsStartupClass(symbols, type) && !SymbolEqualityComparer.Default.Equals(entryPoint?.ContainingType, type)) { // Not a startup class, nothing to do. return; } // 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); var services = new ServicesAnalyzer(builder); var options = new OptionsAnalyzer(builder); var middleware = new MiddlewareAnalyzer(builder); context.RegisterOperationBlockStartAction(context => { if (context.OwningSymbol.Kind != SymbolKind.Method) { return; } var method = (IMethodSymbol)context.OwningSymbol; var isConfigureServices = StartupFacts.IsConfigureServices(symbols, method); if (isConfigureServices) { OnConfigureServicesMethodFound(method); } // In the future we can consider looking at more methods, but for now limit to Main, implicit Main, and Configure* methods var isMain = SymbolEqualityComparer.Default.Equals(entryPoint, context.OwningSymbol); if (isConfigureServices || isMain) { services.AnalyzeConfigureServices(context); options.AnalyzeConfigureServices(context); } var isConfigure = StartupFacts.IsConfigure(symbols, method); if (isConfigure) { OnConfigureMethodFound(method); } if (isConfigure || isMain) { middleware.AnalyzeConfigureMethod(context); } }); // Run after analyses have had a chance to finish to add diagnostics. context.RegisterSymbolEndAction(context => { var analysis = builder.Build(); new UseMvcAnalyzer(analysis).AnalyzeSymbol(context); new BuildServiceProviderAnalyzer(analysis).AnalyzeSymbol(context); new UseAuthorizationAnalyzer(analysis).AnalyzeSymbol(context); }); }, SymbolKind.NamedType); }