// Copyright (c) Microsoft. 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.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Collections;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Diagnostics
{
///
/// Driver to execute diagnostic analyzers for a given compilation.
/// It uses a of s to drive its analysis.
///
internal abstract class AnalyzerDriver : IDisposable
{
internal static readonly ConditionalWeakTable SuppressMessageStateByCompilation = new ConditionalWeakTable();
// Protect against vicious analyzers that provide large values for SymbolKind.
private const int MaxSymbolKind = 100;
private readonly ImmutableArray _analyzers;
private readonly CancellationTokenRegistration _queueRegistration;
protected readonly AnalyzerManager analyzerManager;
// Lazy fields initialized in Initialize() API
private Compilation _compilation;
protected AnalyzerExecutor analyzerExecutor;
internal AnalyzerActions analyzerActions;
private ImmutableDictionary>> _symbolActionsByKind;
private ImmutableDictionary> _semanticModelActionsMap;
private ImmutableDictionary> _compilationEndActionsMap;
///
/// Primary driver task which processes all events, runs analyzer actions and signals completion of at the end.
///
private Task _primaryTask;
///
/// The compilation queue to create the compilation with via WithEventQueue.
///
public AsyncQueue CompilationEventQueue
{
get;
}
///
/// An async queue that is fed the diagnostics as they are computed.
///
public AsyncQueue DiagnosticQueue
{
get;
}
///
/// Initializes the compilation for the analyzer driver.
/// It also computes and initializes and .
/// Finally, it initializes and starts the for the driver.
///
///
/// NOTE: This method must only be invoked from .
///
private void Initialize(Compilation comp, AnalyzerExecutor analyzerExecutor, CancellationToken cancellationToken)
{
try
{
Debug.Assert(_compilation == null);
Debug.Assert(comp.EventQueue == this.CompilationEventQueue);
_compilation = comp;
this.analyzerExecutor = analyzerExecutor;
// Compute the set of effective actions based on suppression, and running the initial analyzers
var analyzerActionsTask = GetAnalyzerActionsAsync(_analyzers, analyzerManager, analyzerExecutor);
var initializeTask = analyzerActionsTask.ContinueWith(t =>
{
this.analyzerActions = t.Result;
_symbolActionsByKind = MakeSymbolActionsByKind();
_semanticModelActionsMap = MakeSemanticModelActionsByAnalyzer();
_compilationEndActionsMap = MakeCompilationEndActionsByAnalyzer();
}, cancellationToken);
// create the primary driver task.
cancellationToken.ThrowIfCancellationRequested();
_primaryTask = Task.Run(async () =>
{
await initializeTask.ConfigureAwait(false);
await ProcessCompilationEventsAsync(cancellationToken).ConfigureAwait(false);
await ExecuteSyntaxTreeActions(cancellationToken).ConfigureAwait(false);
}, cancellationToken)
.ContinueWith(c => DiagnosticQueue.TryComplete(), cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}
finally
{
if (_primaryTask == null)
{
// Set primaryTask to be a cancelled task.
var tcs = new TaskCompletionSource();
tcs.SetCanceled();
_primaryTask = tcs.Task;
// Try to set the DiagnosticQueue to be complete.
this.DiagnosticQueue.TryComplete();
}
}
}
private Task ExecuteSyntaxTreeActions(CancellationToken cancellationToken)
{
// Execute syntax tree analyzers in parallel.
var tasks = ArrayBuilder.GetInstance();
foreach (var tree in _compilation.SyntaxTrees)
{
var actionsByAnalyzers = this.analyzerActions.SyntaxTreeActions.GroupBy(action => action.Analyzer);
foreach (var analyzerAndActions in actionsByAnalyzers)
{
var task = Task.Run(() =>
{
// Execute actions for a given analyzer sequentially.
analyzerExecutor.ExecuteSyntaxTreeActions(analyzerAndActions.ToImmutableArray(), tree);
}, cancellationToken);
tasks.Add(task);
}
}
return Task.WhenAll(tasks.ToArrayAndFree());
}
///
/// Create an and attach it to the given compilation.
///
/// The compilation to which the new driver should be attached.
/// The set of analyzers to include in the analysis.
/// Options that are passed to analyzers.
/// AnalyzerManager to manage analyzers for the lifetime of analyzer host.
/// Delegate to add diagnostics generated for exceptions from third party analyzers.
/// The new compilation with the analyzer driver attached.
/// A cancellation token that can be used to abort analysis.
/// A newly created analyzer driver
///
/// Note that since a compilation is immutable, the act of creating a driver and attaching it produces
/// a new compilation. Any further actions on the compilation should use the new compilation.
///
public static AnalyzerDriver Create(
Compilation compilation,
ImmutableArray analyzers,
AnalyzerOptions options,
AnalyzerManager analyzerManager,
Action addExceptionDiagnostic,
out Compilation newCompilation,
CancellationToken cancellationToken)
{
if (compilation == null)
{
throw new ArgumentNullException(nameof(compilation));
}
if (analyzers.IsDefaultOrEmpty)
{
throw new ArgumentException(CodeAnalysisResources.ArgumentCannotBeEmpty, nameof(analyzers));
}
if (analyzers.Any(a => a == null))
{
throw new ArgumentException(CodeAnalysisResources.ArgumentElementCannotBeNull, nameof(analyzers));
}
Action onAnalyzerException = (ex, analyzer, diagnostic) =>
{
if (addExceptionDiagnostic != null)
{
addExceptionDiagnostic(diagnostic);
}
};
return Create(compilation, analyzers, options, analyzerManager, onAnalyzerException, out newCompilation, cancellationToken: cancellationToken);
}
// internal for testing purposes
internal static AnalyzerDriver Create(
Compilation compilation,
ImmutableArray analyzers,
AnalyzerOptions options,
AnalyzerManager analyzerManager,
Action onAnalyzerException,
out Compilation newCompilation,
CancellationToken cancellationToken)
{
options = options ?? AnalyzerOptions.Empty;
AnalyzerDriver analyzerDriver = compilation.AnalyzerForLanguage(analyzers, analyzerManager, cancellationToken);
newCompilation = compilation.WithEventQueue(analyzerDriver.CompilationEventQueue);
var addDiagnostic = GetDiagnosticSinkWithSuppression(analyzerDriver.DiagnosticQueue.Enqueue, newCompilation);
Action newOnAnalyzerException;
if (onAnalyzerException != null)
{
// Wrap onAnalyzerException to pass in filtered diagnostic.
var comp = newCompilation;
newOnAnalyzerException = (ex, analyzer, diagnostic) =>
onAnalyzerException(ex, analyzer, GetFilteredDiagnostic(diagnostic, comp));
}
else
{
// Add exception diagnostic to regular diagnostic bag.
newOnAnalyzerException = (ex, analyzer, diagnostic) => addDiagnostic(diagnostic);
}
var analyzerExecutor = AnalyzerExecutor.Create(newCompilation, options, addDiagnostic, newOnAnalyzerException, cancellationToken);
analyzerDriver.Initialize(newCompilation, analyzerExecutor, cancellationToken);
return analyzerDriver;
}
///
/// Create an analyzer driver.
///
/// The set of analyzers to include in the analysis
/// AnalyzerManager to manage analyzers for analyzer host's lifetime.
/// a cancellation token that can be used to abort analysis
protected AnalyzerDriver(ImmutableArray analyzers, AnalyzerManager analyzerManager, CancellationToken cancellationToken)
{
_analyzers = analyzers;
this.analyzerManager = analyzerManager;
this.CompilationEventQueue = new AsyncQueue();
this.DiagnosticQueue = new AsyncQueue();
_queueRegistration = cancellationToken.Register(() =>
{
this.CompilationEventQueue.TryComplete();
this.DiagnosticQueue.TryComplete();
});
}
///
/// Returns all diagnostics computed by the analyzers since the last time this was invoked.
/// If has been completed with all compilation events, then it waits for
/// task for the driver to finish processing all events and generate remaining analyzer diagnostics.
///
public async Task> GetDiagnosticsAsync()
{
var allDiagnostics = DiagnosticBag.GetInstance();
if (CompilationEventQueue.IsCompleted)
{
await this.WhenCompletedTask.ConfigureAwait(false);
}
Diagnostic d;
while (DiagnosticQueue.TryDequeue(out d))
{
allDiagnostics.Add(d);
}
var diagnostics = allDiagnostics.ToReadOnlyAndFree();
// Verify that the diagnostics are already filtered.
Debug.Assert(_compilation == null ||
diagnostics.All(diag => _compilation.FilterDiagnostic(diag)?.Severity == diag.Severity));
return diagnostics;
}
///
/// Return a task that completes when the driver is done producing diagnostics.
///
public Task WhenCompletedTask => _primaryTask;
private ImmutableDictionary>> MakeSymbolActionsByKind()
{
var builder = ImmutableDictionary.CreateBuilder>>();
var actionsByAnalyzers = this.analyzerActions.SymbolActions.GroupBy(action => action.Analyzer);
foreach (var analyzerAndActions in actionsByAnalyzers)
{
var actionsByKindBuilder = new List>();
foreach (var symbolAction in analyzerAndActions)
{
var kinds = symbolAction.Kinds;
foreach (int kind in kinds.Distinct())
{
if (kind > MaxSymbolKind) continue; // protect against vicious analyzers
while (kind >= actionsByKindBuilder.Count)
{
actionsByKindBuilder.Add(ArrayBuilder.GetInstance());
}
actionsByKindBuilder[kind].Add(symbolAction);
}
}
var actionsByKind = actionsByKindBuilder.Select(a => a.ToImmutableAndFree()).ToImmutableArray();
builder.Add(analyzerAndActions.Key, actionsByKind);
}
return builder.ToImmutable();
}
private ImmutableDictionary> MakeSemanticModelActionsByAnalyzer()
{
var builder = ImmutableDictionary.CreateBuilder>();
var actionsByAnalyzers = this.analyzerActions.SemanticModelActions.GroupBy(action => action.Analyzer);
foreach (var analyzerAndActions in actionsByAnalyzers)
{
builder.Add(analyzerAndActions.Key, analyzerAndActions.ToImmutableArray());
}
return builder.ToImmutable();
}
private ImmutableDictionary> MakeCompilationEndActionsByAnalyzer()
{
var builder = ImmutableDictionary.CreateBuilder>();
var actionsByAnalyzers = this.analyzerActions.CompilationEndActions.GroupBy(action => action.Analyzer);
foreach (var analyzerAndActions in actionsByAnalyzers)
{
builder.Add(analyzerAndActions.Key, analyzerAndActions.ToImmutableArray());
}
return builder.ToImmutable();
}
private async Task ProcessCompilationEventsAsync(CancellationToken cancellationToken)
{
while (!CompilationEventQueue.IsCompleted || CompilationEventQueue.Count > 0)
{
CompilationEvent e;
try
{
e = await CompilationEventQueue.DequeueAsync(cancellationToken).ConfigureAwait(false);
}
catch (TaskCanceledException)
{
// When the queue is completed with a pending DequeueAsync return then a
// TaskCanceledException will be thrown. This just signals the queue is
// complete and we should finish processing it.
Debug.Assert(CompilationEventQueue.IsCompleted, "DequeueAsync should never throw unless the AsyncQueue is completed.");
break;
}
if (e.Compilation != _compilation)
{
Debug.Assert(false, "CompilationEvent with a different compilation then driver's compilation?");
continue;
}
try
{
await ProcessEventAsync(e, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// when just a single operation is cancelled, we continue processing events.
// TODO: what is the desired behavior in this case?
}
}
}
private async Task ProcessEventAsync(CompilationEvent e, CancellationToken cancellationToken)
{
var symbolEvent = e as SymbolDeclaredCompilationEvent;
if (symbolEvent != null)
{
await ProcessSymbolDeclaredAsync(symbolEvent, cancellationToken).ConfigureAwait(false);
return;
}
var completedEvent = e as CompilationUnitCompletedEvent;
if (completedEvent != null)
{
await ProcessCompilationUnitCompleted(completedEvent, cancellationToken).ConfigureAwait(false);
return;
}
var endEvent = e as CompilationCompletedEvent;
if (endEvent != null)
{
await ProcessCompilationCompletedAsync(endEvent, cancellationToken).ConfigureAwait(false);
return;
}
if (e is CompilationStartedEvent)
{
// Ignore CompilationStartedEvent.
return;
}
throw new InvalidOperationException("Unexpected compilation event of type " + e.GetType().Name);
}
private async Task ProcessSymbolDeclaredAsync(SymbolDeclaredCompilationEvent symbolEvent, CancellationToken cancellationToken)
{
// Create a task per-analyzer to execute analyzer actions.
// We execute analyzers in parallel, but for a given analyzer we execute actions sequentially.
var tasksMap = PooledDictionary.GetInstance();
try
{
var symbol = symbolEvent.Symbol;
// Skip symbol actions for implicitly declared symbols.
if (!symbol.IsImplicitlyDeclared)
{
AddTasksForExecutingSymbolActions(symbolEvent, tasksMap, cancellationToken);
}
// Skip syntax actions for implicitly declared symbols, except for implicitly declared global namespace symbols.
if (!symbol.IsImplicitlyDeclared ||
(symbol.Kind == SymbolKind.Namespace && ((INamespaceSymbol)symbol).IsGlobalNamespace))
{
AddTasksForExecutingDeclaringReferenceActions(symbolEvent, tasksMap, cancellationToken);
}
// Execute all analyzer actions.
await Task.WhenAll(tasksMap.Values).ConfigureAwait(false);
}
finally
{
tasksMap.Free();
symbolEvent.FlushCache();
}
}
private void AddTasksForExecutingSymbolActions(SymbolDeclaredCompilationEvent symbolEvent, IDictionary taskMap, CancellationToken cancellationToken)
{
var symbol = symbolEvent.Symbol;
Action addDiagnosticForSymbol = GetDiagnosticSinkWithSuppression(DiagnosticQueue.Enqueue, _compilation, symbol);
foreach (var analyzerAndActions in _symbolActionsByKind)
{
var analyzer = analyzerAndActions.Key;
var actionsByKind = analyzerAndActions.Value;
Action executeSymbolActionsForAnalyzer = () =>
ExecuteSymbolActionsForAnalyzer(symbol, analyzer, actionsByKind, addDiagnosticForSymbol, cancellationToken);
AddAnalyzerActionsExecutor(taskMap, analyzer, executeSymbolActionsForAnalyzer, cancellationToken);
}
}
private void ExecuteSymbolActionsForAnalyzer(
ISymbol symbol,
DiagnosticAnalyzer analyzer,
ImmutableArray> actionsByKind,
Action addDiagnosticForSymbol,
CancellationToken cancellationToken)
{
// Invoke symbol analyzers only for source symbols.
var declaringSyntaxRefs = symbol.DeclaringSyntaxReferences;
if ((int)symbol.Kind < actionsByKind.Length && declaringSyntaxRefs.Any(s => s.SyntaxTree != null))
{
analyzerExecutor.ExecuteSymbolActions(actionsByKind[(int)symbol.Kind], symbol, addDiagnosticForSymbol);
}
}
protected static void AddAnalyzerActionsExecutor(IDictionary map, DiagnosticAnalyzer analyzer, Action executeAnalyzerActions, CancellationToken cancellationToken)
{
Task currentTask;
if (!map.TryGetValue(analyzer, out currentTask))
{
map[analyzer] = Task.Run(executeAnalyzerActions, cancellationToken);
}
else
{
map[analyzer] = currentTask.ContinueWith(_ => executeAnalyzerActions(), cancellationToken);
}
}
protected abstract void AddTasksForExecutingDeclaringReferenceActions(SymbolDeclaredCompilationEvent symbolEvent, IDictionary taskMap, CancellationToken cancellationToken);
private Task ProcessCompilationUnitCompleted(CompilationUnitCompletedEvent completedEvent, CancellationToken cancellationToken)
{
// When the compiler is finished with a compilation unit, we can run user diagnostics which
// might want to ask the compiler for all the diagnostics in the source file, for example
// to get information about unnecessary usings.
try
{
// Execute analyzers in parallel.
var tasks = ArrayBuilder.GetInstance();
var semanticModel = completedEvent.SemanticModel;
foreach (var analyzerAndActions in _semanticModelActionsMap)
{
var task = Task.Run(() =>
{
// Execute actions for a given analyzer sequentially.
analyzerExecutor.ExecuteSemanticModelActions(analyzerAndActions.Value, semanticModel);
}, cancellationToken);
tasks.Add(task);
}
return Task.WhenAll(tasks.ToArrayAndFree());
}
finally
{
completedEvent.FlushCache();
}
}
private async Task ProcessCompilationCompletedAsync(CompilationCompletedEvent endEvent, CancellationToken cancellationToken)
{
try
{
// Execute analyzers in parallel.
var tasks = ArrayBuilder.GetInstance();
foreach (var analyzerAndActions in _compilationEndActionsMap)
{
var task = Task.Run(() =>
{
// Execute actions for a given analyzer sequentially.
analyzerExecutor.ExecuteCompilationEndActions(analyzerAndActions.Value);
}, cancellationToken);
tasks.Add(task);
}
await Task.WhenAll(tasks.ToArrayAndFree()).ConfigureAwait(false);
}
finally
{
endEvent.FlushCache();
}
}
internal static Action GetDiagnosticSinkWithSuppression(Action addDiagnosticCore, Compilation compilation, ISymbol symbolOpt = null)
{
return diagnostic =>
{
var filteredDiagnostic = GetFilteredDiagnostic(diagnostic, compilation, symbolOpt);
if (filteredDiagnostic != null)
{
addDiagnosticCore(filteredDiagnostic);
}
};
}
private static Diagnostic GetFilteredDiagnostic(Diagnostic diagnostic, Compilation compilation, ISymbol symbolOpt = null)
{
var filteredDiagnostic = compilation.FilterDiagnostic(diagnostic);
if (filteredDiagnostic != null)
{
var suppressMessageState = SuppressMessageStateByCompilation.GetValue(compilation, (c) => new SuppressMessageAttributeState(c));
if (suppressMessageState.IsDiagnosticSuppressed(filteredDiagnostic, symbolOpt: symbolOpt))
{
return null;
}
}
return filteredDiagnostic;
}
private static Task GetAnalyzerActionsAsync(
ImmutableArray analyzers,
AnalyzerManager analyzerManager,
AnalyzerExecutor analyzerExecutor)
{
return Task.Run(async () =>
{
AnalyzerActions allAnalyzerActions = new AnalyzerActions();
foreach (var analyzer in analyzers)
{
if (!IsDiagnosticAnalyzerSuppressed(analyzer, analyzerExecutor.Compilation.Options, analyzerManager, analyzerExecutor))
{
var analyzerActions = await analyzerManager.GetAnalyzerActionsAsync(analyzer, analyzerExecutor).ConfigureAwait(false);
allAnalyzerActions = allAnalyzerActions.Append(analyzerActions);
}
}
return allAnalyzerActions;
}, analyzerExecutor.CancellationToken);
}
///
/// Returns true if all the diagnostics that can be produced by this analyzer are suppressed through options.
///
internal static bool IsDiagnosticAnalyzerSuppressed(
DiagnosticAnalyzer analyzer,
CompilationOptions options,
AnalyzerManager analyzerManager,
AnalyzerExecutor analyzerExecutor)
{
return analyzerManager.IsDiagnosticAnalyzerSuppressed(analyzer, options, IsCompilerAnalyzer, analyzerExecutor);
}
private static bool IsCompilerAnalyzer(DiagnosticAnalyzer analyzer)
{
return analyzer is CompilerDiagnosticAnalyzer;
}
public void Dispose()
{
this.CompilationEventQueue.TryComplete();
this.DiagnosticQueue.TryComplete();
_queueRegistration.Dispose();
}
}
///
/// Driver to execute diagnostic analyzers for a given compilation.
/// It uses a of s to drive its analysis.
///
internal class AnalyzerDriver : AnalyzerDriver where TLanguageKindEnum : struct
{
private Func _getKind;
private ImmutableDictionary>>> _lazyNodeActionsByKind = null;
private ImmutableDictionary>> _lazyCodeBlockStartActionsByAnalyzer = null;
private ImmutableDictionary> _lazyCodeBlockEndActionsByAnalyzer = null;
///
/// Create an analyzer driver.
///
/// The set of analyzers to include in the analysis
/// A delegate that returns the language-specific kind for a given syntax node
/// AnalyzerManager to manage analyzers for the lifetime of analyzer host.
/// a cancellation token that can be used to abort analysis
internal AnalyzerDriver(ImmutableArray analyzers, Func getKind, AnalyzerManager analyzerManager, CancellationToken cancellationToken) : base(analyzers, analyzerManager, cancellationToken)
{
_getKind = getKind;
}
private ImmutableDictionary>>> NodeActionsByKind
{
get
{
if (_lazyNodeActionsByKind == null)
{
var nodeActions = this.analyzerActions.GetSyntaxNodeActions();
ImmutableDictionary>>> analyzerActionsByKind;
if (nodeActions.Any())
{
var nodeActionsByAnalyzers = nodeActions.GroupBy(a => a.Analyzer);
var builder = ImmutableDictionary.CreateBuilder>>>();
foreach (var analzerAndActions in nodeActionsByAnalyzers)
{
ImmutableDictionary>> actionsByKind;
if (analzerAndActions.Any())
{
actionsByKind = AnalyzerExecutor.GetNodeActionsByKind(analzerAndActions);
}
else
{
actionsByKind = ImmutableDictionary>>.Empty;
}
builder.Add(analzerAndActions.Key, actionsByKind);
}
analyzerActionsByKind = builder.ToImmutable();
}
else
{
analyzerActionsByKind = ImmutableDictionary>>>.Empty;
}
Interlocked.CompareExchange(ref _lazyNodeActionsByKind, analyzerActionsByKind, null);
}
return _lazyNodeActionsByKind;
}
}
private ImmutableDictionary>> CodeBlockStartActionsByAnalyer
{
get
{
if (_lazyCodeBlockStartActionsByAnalyzer == null)
{
ImmutableDictionary>> codeBlockStartActionsByAnalyzer;
var codeBlockStartActions = this.analyzerActions.GetCodeBlockStartActions();
if (codeBlockStartActions.Any())
{
var builder = ImmutableDictionary.CreateBuilder>>();
var actionsByAnalyzer = codeBlockStartActions.GroupBy(action => action.Analyzer);
foreach (var analyzerAndActions in actionsByAnalyzer)
{
builder.Add(analyzerAndActions.Key, analyzerAndActions.ToImmutableArrayOrEmpty());
}
codeBlockStartActionsByAnalyzer = builder.ToImmutable();
}
else
{
codeBlockStartActionsByAnalyzer = ImmutableDictionary>>.Empty;
}
Interlocked.CompareExchange(ref _lazyCodeBlockStartActionsByAnalyzer, codeBlockStartActionsByAnalyzer, null);
}
return _lazyCodeBlockStartActionsByAnalyzer;
}
}
private ImmutableDictionary> CodeBlockEndActionsByAnalyer
{
get
{
if (_lazyCodeBlockEndActionsByAnalyzer == null)
{
ImmutableDictionary> codeBlockEndActionsByAnalyzer;
var codeBlockEndActions = this.analyzerActions.CodeBlockEndActions;
if (codeBlockEndActions.Any())
{
var builder = ImmutableDictionary.CreateBuilder>();
var actionsByAnalyzer = codeBlockEndActions.GroupBy(action => action.Analyzer);
foreach (var analyzerAndActions in actionsByAnalyzer)
{
builder.Add(analyzerAndActions.Key, analyzerAndActions.ToImmutableArrayOrEmpty());
}
codeBlockEndActionsByAnalyzer = builder.ToImmutable();
}
else
{
codeBlockEndActionsByAnalyzer = ImmutableDictionary>.Empty;
}
Interlocked.CompareExchange(ref _lazyCodeBlockEndActionsByAnalyzer, codeBlockEndActionsByAnalyzer, null);
}
return _lazyCodeBlockEndActionsByAnalyzer;
}
}
protected override void AddTasksForExecutingDeclaringReferenceActions(
SymbolDeclaredCompilationEvent symbolEvent,
IDictionary taskMap,
CancellationToken cancellationToken)
{
var symbol = symbolEvent.Symbol;
var executeSyntaxNodeActions = this.NodeActionsByKind.Any();
var executeCodeBlockActions = AnalyzerExecutor.CanHaveExecutableCodeBlock(symbol) && (this.CodeBlockStartActionsByAnalyer.Any() || this.CodeBlockEndActionsByAnalyer.Any());
if (executeSyntaxNodeActions || executeCodeBlockActions)
{
foreach (var decl in symbol.DeclaringSyntaxReferences)
{
AddTasksForExecutingDeclaringReferenceActions(decl, symbolEvent, taskMap,
executeSyntaxNodeActions, executeCodeBlockActions, cancellationToken);
}
}
}
private void AddTasksForExecutingDeclaringReferenceActions(
SyntaxReference decl,
SymbolDeclaredCompilationEvent symbolEvent,
IDictionary taskMap,
bool shouldExecuteSyntaxNodeActions,
bool shouldExecuteCodeBlockActions,
CancellationToken cancellationToken)
{
Debug.Assert(shouldExecuteSyntaxNodeActions || shouldExecuteCodeBlockActions);
var symbol = symbolEvent.Symbol;
SemanticModel semanticModel = symbolEvent.SemanticModel(decl);
var declaringReferenceSyntax = decl.GetSyntax(cancellationToken);
var syntax = semanticModel.GetTopmostNodeForDiagnosticAnalysis(symbol, declaringReferenceSyntax);
// We only care about the top level symbol declaration and its immediate member declarations.
int? levelsToCompute = 2;
var declarationsInNode = semanticModel.GetDeclarationsInNode(syntax, getSymbol: syntax != declaringReferenceSyntax, cancellationToken: cancellationToken, levelsToCompute: levelsToCompute);
// Execute stateless syntax node actions.
if (shouldExecuteSyntaxNodeActions)
{
foreach (var analyzerAndActions in this.NodeActionsByKind)
{
Action executeStatelessNodeActions = () =>
ExecuteStatelessNodeActions(analyzerAndActions.Value, syntax, symbol, declarationsInNode, semanticModel,
_getKind, analyzerExecutor);
AddAnalyzerActionsExecutor(taskMap, analyzerAndActions.Key, executeStatelessNodeActions, cancellationToken);
}
}
// Execute code block actions.
if (shouldExecuteCodeBlockActions)
{
// Compute the executable code blocks of interest.
var executableCodeBlocks = ImmutableArray.Empty;
foreach (var declInNode in declarationsInNode)
{
if (declInNode.DeclaredNode == syntax || declInNode.DeclaredNode == declaringReferenceSyntax)
{
executableCodeBlocks = declInNode.ExecutableCodeBlocks;
break;
}
}
if (executableCodeBlocks.Any())
{
foreach (var analyzerAndActions in this.CodeBlockStartActionsByAnalyer)
{
Action executeCodeBlockActions = () =>
{
var codeBlockStartActions = analyzerAndActions.Value;
var codeBlockEndActions = this.CodeBlockEndActionsByAnalyer.ContainsKey(analyzerAndActions.Key) ?
this.CodeBlockEndActionsByAnalyer[analyzerAndActions.Key] :
ImmutableArray.Empty;
analyzerExecutor.ExecuteCodeBlockActions(codeBlockStartActions, codeBlockEndActions,
syntax, symbol, executableCodeBlocks, semanticModel, _getKind);
};
AddAnalyzerActionsExecutor(taskMap, analyzerAndActions.Key, executeCodeBlockActions, cancellationToken);
}
// Execute code block end actions for analyzers which don't have corresponding code block start actions.
foreach (var analyzerAndActions in this.CodeBlockEndActionsByAnalyer)
{
// skip analyzers executed above.
if (!CodeBlockStartActionsByAnalyer.ContainsKey(analyzerAndActions.Key))
{
Action executeCodeBlockActions = () =>
{
var codeBlockStartActions = ImmutableArray>.Empty;
var codeBlockEndActions = analyzerAndActions.Value;
analyzerExecutor.ExecuteCodeBlockActions(codeBlockStartActions, codeBlockEndActions,
syntax, symbol, executableCodeBlocks, semanticModel, _getKind);
};
AddAnalyzerActionsExecutor(taskMap, analyzerAndActions.Key, executeCodeBlockActions, cancellationToken);
}
}
}
}
}
private static void ExecuteStatelessNodeActions(
IDictionary>> actionsByKind,
SyntaxNode declaredNode,
ISymbol declaredSymbol,
IEnumerable declarationsInNode,
SemanticModel semanticModel,
Func getKind,
AnalyzerExecutor analyzerExecutor)
{
// Eliminate syntax nodes for descendant member declarations within declarations.
// There will be separate symbols declared for the members, hence we avoid duplicate syntax analysis by skipping these here.
HashSet descendantDeclsToSkip = null;
bool first = true;
foreach (var declInNode in declarationsInNode)
{
if (declInNode.DeclaredNode != declaredNode)
{
// Might be a field declaration statement with multiple fields declared.
// If so, we execute syntax node analysis for entire field declaration (and its descendants)
// if we processing the first field and skip syntax actions for remaining fields in the declaration.
if (declInNode.DeclaredSymbol == declaredSymbol)
{
if (!first)
{
return;
}
break;
}
// Compute the topmost node representing the syntax declaration for the member that needs to be skipped.
var declarationNodeToSkip = declInNode.DeclaredNode;
var declaredSymbolOfDeclInNode = declInNode.DeclaredSymbol ?? semanticModel.GetDeclaredSymbol(declInNode.DeclaredNode, analyzerExecutor.CancellationToken);
if (declaredSymbolOfDeclInNode != null)
{
declarationNodeToSkip = semanticModel.GetTopmostNodeForDiagnosticAnalysis(declaredSymbolOfDeclInNode, declInNode.DeclaredNode);
}
descendantDeclsToSkip = descendantDeclsToSkip ?? new HashSet();
descendantDeclsToSkip.Add(declarationNodeToSkip);
}
first = false;
}
var nodesToAnalyze = descendantDeclsToSkip == null ?
declaredNode.DescendantNodesAndSelf(descendIntoTrivia: true) :
declaredNode.DescendantNodesAndSelf(n => !descendantDeclsToSkip.Contains(n), descendIntoTrivia: true).Except(descendantDeclsToSkip);
analyzerExecutor.ExecuteSyntaxNodeActions(nodesToAnalyze, actionsByKind, semanticModel, getKind);
}
}
internal static class AnalyzerDriverResources
{
internal static string AnalyzerFailure => CodeAnalysisResources.CompilerAnalyzerFailure;
internal static string AnalyzerThrows => CodeAnalysisResources.CompilerAnalyzerThrows;
internal static string DiagnosticDescriptorThrows => CodeAnalysisResources.DiagnosticDescriptorThrows;
internal static string ArgumentElementCannotBeNull => CodeAnalysisResources.ArgumentElementCannotBeNull;
internal static string ArgumentCannotBeEmpty => CodeAnalysisResources.ArgumentCannotBeEmpty;
}
}