public static bool IsConfigureServices(StartupSymbols symbols, IMethodSymbol symbol) { if (symbol == null) { throw new ArgumentNullException(nameof(symbol)); } if (symbol.DeclaredAccessibility != Accessibility.Public) { return(false); } if (!string.Equals(symbol.Name, "ConfigureServices", StringComparison.Ordinal)) { return(false); } if (symbol.Parameters.Length != 1) { return(false); } if (symbol.Parameters[0].Type != symbols.IServiceCollection) { return(false); } return(true); }
private void OnCompilationStart(CompilationStartAnalysisContext context) { var symbols = new StartupSymbols(context.Compilation); // Don't run analyzer if ASP.NET Core types cannot be found if (symbols.IServiceCollection == null || symbols.IApplicationBuilder == null) { return; } // This analyzer is a general-purpose framework that functions by: // 1. Discovering Startup methods (ConfigureServices, Configure) // 2. Launching additional analyses of these Startup methods and collecting results // 3. Running a final pass to add diagnostics based on computed state var analyses = new ConcurrentBag <StartupComputedAnalysis>(); context.RegisterOperationBlockStartAction(context => { AnalyzeStartupMethods(context, symbols, analyses); }); // Run after analyses have had a chance to finish to add diagnostics. context.RegisterCompilationEndAction(analysisContext => { RunAnalysis(analysisContext, analyses); }); }
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); }
public StartupAnalysis( StartupSymbols startupSymbols, ImmutableDictionary <INamedTypeSymbol, ImmutableArray <object> > analysesByType) { StartupSymbols = startupSymbols; _analysesByType = analysesByType; }
// 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); }
// 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); }
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; } context.RegisterSymbolStartAction(context => { var type = (INamedTypeSymbol)context.Symbol; if (!StartupFacts.IsStartupClass(symbols, 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; if (StartupFacts.IsConfigureServices(symbols, method)) { OnConfigureServicesMethodFound(method); 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 => { var analysis = builder.Build(); new UseMvcAnalyzer(analysis).AnalyzeSymbol(context); new BuildServiceProviderValidator(analysis).AnalyzeSymbol(context); }); }, SymbolKind.NamedType); }
public async Task FindConfigureMethods_AtDifferentScopes() { // Arrange 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 = await CreateCompilationAsync("Startup"); 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); }
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("Configure", StringComparison.Ordinal)) { return(false); } // IApplicationBuilder can appear in any parameter for (var i = 0; i < symbol.Parameters.Length; i++) { if (symbol.Parameters[i].Type == symbols.IApplicationBuilder) { return(true); } } return(false); }
public StartupAnalyzerContext(StartupAnalzyer analyzer, StartupSymbols startupSymbols) { _analyzer = analyzer; StartupSymbols = startupSymbols; _analysesByType = new Dictionary <INamedTypeSymbol, List <object> >(); _lock = new object(); }
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 async Task IsStartupClass_RejectsNotStartupClass(string source) { // Arrange var compilation = await CreateCompilationAsync(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 async Task DetectFeaturesAsync_FindsSignalR(string source) { // Arrange var compilation = await CreateCompilationAsync(source); var symbols = new StartupSymbols(compilation); var type = (INamedTypeSymbol)compilation.GetSymbolsWithName(source).Single(); Assert.True(StartupFacts.IsStartupClass(symbols, type)); // Act var features = await CompilationFeatureDetector.DetectFeaturesAsync(compilation); // Assert Assert.Collection(features, f => Assert.Equal(WellKnownFeatures.SignalR, f)); }
public async Task DetectFeaturesAsync_FindsNoFeatures() { // Arrange var compilation = await CreateCompilationAsync(nameof(StartupWithNoFeatures)); var symbols = new StartupSymbols(compilation); var type = (INamedTypeSymbol)compilation.GetSymbolsWithName(nameof(StartupWithNoFeatures)).Single(); Assert.True(StartupFacts.IsStartupClass(symbols, type)); // Act var features = await CompilationFeatureDetector.DetectFeaturesAsync(compilation); // Assert Assert.Empty(features); }
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); // 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 IsConfigure_FindsConfigureMethod(string source, string methodName) { // Arrange var compilation = await CreateCompilationAsync(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.True(result); } }
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); }
#pragma warning disable RS1012 // Start action has no registered actions. public StartupAnalysisContext(OperationBlockStartAnalysisContext operationBlockStartAnalysisContext, StartupSymbols startupSymbols) { OperationBlockStartAnalysisContext = operationBlockStartAnalysisContext; StartupSymbols = startupSymbols; }
private void AnalyzeStartupMethods(OperationBlockStartAnalysisContext context, StartupSymbols symbols, ConcurrentBag <StartupComputedAnalysis> analyses) { if (!IsStartupFile(context)) { return; } if (context.OwningSymbol.Kind != SymbolKind.Method) { return; } var startupAnalysisContext = new StartupAnalysisContext(context, symbols); var method = (IMethodSymbol)context.OwningSymbol; if (StartupFacts.IsConfigureServices(symbols, method)) { for (var i = 0; i < ConfigureServicesMethodAnalysisFactories.Length; i++) { var analysis = ConfigureServicesMethodAnalysisFactories[i].Invoke(startupAnalysisContext); analyses.Add(analysis); OnAnalysisStarted(analysis); } OnConfigureServicesMethodFound(method); } if (StartupFacts.IsConfigure(symbols, method)) { for (var i = 0; i < ConfigureMethodAnalysisFactories.Length; i++) { var analysis = ConfigureMethodAnalysisFactories[i].Invoke(startupAnalysisContext); analyses.Add(analysis); OnAnalysisStarted(analysis); } OnConfigureMethodFound(method); } }
private 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); }