private ImmutableArray <DiagnosticDescriptor> GetDiagnosticDescriptorsCore(DiagnosticAnalyzer analyzer) { ImmutableArray <DiagnosticDescriptor> descriptors; try { // SupportedDiagnostics is user code and can throw an exception. descriptors = analyzer.SupportedDiagnostics; if (descriptors.IsDefault) { descriptors = ImmutableArray <DiagnosticDescriptor> .Empty; } } catch (Exception ex) { AnalyzerHelper.OnAnalyzerExceptionForSupportedDiagnostics(analyzer, ex, _hostDiagnosticUpdateSource); descriptors = ImmutableArray <DiagnosticDescriptor> .Empty; } return(descriptors); }
public void OnAnalyzerLoadFailed(object sender, AnalyzerLoadFailureEventArgs e) { if (!(sender is AnalyzerFileReference reference)) { return; } var diagnostic = AnalyzerHelper.CreateAnalyzerLoadFailureDiagnostic(e, reference.FullPath, projectId: null, language: null); // diagnostic from host analyzer can never go away var args = DiagnosticsUpdatedArgs.DiagnosticsCreated( id: Tuple.Create(this, reference.FullPath, e.ErrorCode, e.TypeName), workspace: _primaryWorkspace.Workspace, solution: null, projectId: null, documentId: null, diagnostics: ImmutableArray.Create <DiagnosticData>(diagnostic)); // this can be null in test. but in product code, this should never be null. _hostUpdateSource?.RaiseDiagnosticsUpdated(args); }
public void OnAnalyzerLoadFailed(object sender, AnalyzerLoadFailureEventArgs e) { var reference = sender as AnalyzerFileReference; if (reference == null) { return; } var diagnostic = AnalyzerHelper.CreateAnalyzerLoadFailureDiagnostic(reference.FullPath, e); // diagnostic from host analyzer can never go away var args = DiagnosticsUpdatedArgs.DiagnosticsCreated( id: Tuple.Create(this, reference.FullPath, e.ErrorCode, e.TypeName), workspace: PrimaryWorkspace.Workspace, solution: null, projectId: null, documentId: null, diagnostics: ImmutableArray.Create <DiagnosticData>(diagnostic)); _hostUpdateSource.RaiseDiagnosticsUpdated(args); }
public async Task <IEnumerable <DiagnosticData> > ComputeDiagnosticsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) { Contract.ThrowIfFalse(AnalysisScope.Analyzers.Contains(analyzer)); var textDocument = AnalysisScope.TextDocument; var span = AnalysisScope.Span; var kind = AnalysisScope.Kind; var document = textDocument as Document; RoslynDebug.Assert(document != null || kind == AnalysisKind.Syntax, "We only support syntactic analysis for non-source documents"); var loadDiagnostic = await textDocument.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); if (analyzer == FileContentLoadAnalyzer.Instance) { return(loadDiagnostic != null? SpecializedCollections.SingletonEnumerable(DiagnosticData.Create(loadDiagnostic, textDocument)) : SpecializedCollections.EmptyEnumerable <DiagnosticData>()); } if (loadDiagnostic != null) { return(SpecializedCollections.EmptyEnumerable <DiagnosticData>()); } if (analyzer is DocumentDiagnosticAnalyzer documentAnalyzer) { if (document == null) { return(SpecializedCollections.EmptyEnumerable <DiagnosticData>()); } var documentDiagnostics = await AnalyzerHelper.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( documentAnalyzer, document, kind, _compilationWithAnalyzers?.Compilation, cancellationToken).ConfigureAwait(false); return(documentDiagnostics.ConvertToLocalDiagnostics(document, span)); } // quick optimization to reduce allocations. if (_compilationWithAnalyzers == null || !analyzer.SupportAnalysisKind(kind)) { if (kind == AnalysisKind.Syntax) { Logger.Log(FunctionId.Diagnostics_SyntaxDiagnostic, (r, d, a, k) => $"Driver: {r != null}, {d.Id}, {d.Project.Id}, {a}, {k}", _compilationWithAnalyzers, textDocument, analyzer, kind); } return(SpecializedCollections.EmptyEnumerable <DiagnosticData>()); } // if project is not loaded successfully then, we disable semantic errors for compiler analyzers var isCompilerAnalyzer = analyzer.IsCompilerAnalyzer(); if (kind != AnalysisKind.Syntax && isCompilerAnalyzer) { var isEnabled = await textDocument.Project.HasSuccessfullyLoadedAsync(cancellationToken).ConfigureAwait(false); Logger.Log(FunctionId.Diagnostics_SemanticDiagnostic, (a, d, e) => $"{a}, ({d.Id}, {d.Project.Id}), Enabled:{e}", analyzer, textDocument, isEnabled); if (!isEnabled) { return(SpecializedCollections.EmptyEnumerable <DiagnosticData>()); } } if (document == null && textDocument is not AdditionalDocument) { // We currently support document analysis only for source documents and additional documents. return(SpecializedCollections.EmptyEnumerable <DiagnosticData>()); } var diagnostics = kind switch { AnalysisKind.Syntax => await GetSyntaxDiagnosticsAsync(analyzer, isCompilerAnalyzer, cancellationToken).ConfigureAwait(false), AnalysisKind.Semantic => await GetSemanticDiagnosticsAsync(analyzer, isCompilerAnalyzer, cancellationToken).ConfigureAwait(false), _ => throw ExceptionUtilities.UnexpectedValue(kind), }; #if DEBUG var diags = await diagnostics.ToDiagnosticsAsync(textDocument.Project, cancellationToken).ConfigureAwait(false); Debug.Assert(diags.Length == CompilationWithAnalyzers.GetEffectiveDiagnostics(diags, _compilationWithAnalyzers.Compilation).Count()); Debug.Assert(diagnostics.Length == diags.ConvertToLocalDiagnostics(textDocument, span).Count()); #endif return(diagnostics); }
private async Task <ImmutableArray <DiagnosticData> > GetSemanticDiagnosticsAsync(DiagnosticAnalyzer analyzer, bool isCompilerAnalyzer, CancellationToken cancellationToken) { // PERF: // 1. Compute diagnostics for all analyzers with a single invocation into CompilationWithAnalyzers. // This is critical for better analyzer execution performance through re-use of bound node cache. // 2. Ensure that the compiler analyzer is treated specially and does not block on diagnostic computation // for rest of the analyzers. This is needed to ensure faster refresh for compiler diagnostics while typing. RoslynDebug.Assert(_compilationWithAnalyzers != null); var span = AnalysisScope.Span; var document = (Document)AnalysisScope.TextDocument; if (isCompilerAnalyzer) { #if DEBUG await VerifySpanBasedCompilerDiagnosticsAsync().ConfigureAwait(false); #endif var adjustedSpan = await GetAdjustedSpanForCompilerAnalyzerAsync().ConfigureAwait(false); return(await GetCompilerAnalyzerDiagnosticsAsync(analyzer, adjustedSpan, cancellationToken).ConfigureAwait(false)); } if (_lazySemanticDiagnostics == null) { var analysisScope = AnalysisScope.WithAnalyzers(_compilationBasedAnalyzersInAnalysisScope); var semanticDiagnostics = await GetAnalysisResultAsync(analysisScope, cancellationToken).ConfigureAwait(false); Interlocked.CompareExchange(ref _lazySemanticDiagnostics, semanticDiagnostics, null); } return(_lazySemanticDiagnostics.TryGetValue(analyzer, out var diagnosticAnalysisResult) ? diagnosticAnalysisResult.GetDocumentDiagnostics(AnalysisScope.TextDocument.Id, AnalysisScope.Kind) : ImmutableArray <DiagnosticData> .Empty); async Task <TextSpan?> GetAdjustedSpanForCompilerAnalyzerAsync() { // This method is to workaround a bug (https://github.com/dotnet/roslyn/issues/1557) // once that bug is fixed, we should be able to use given span as it is. Debug.Assert(isCompilerAnalyzer); if (!span.HasValue) { return(null); } var service = document.GetRequiredLanguageService <ISyntaxFactsService>(); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var startNode = service.GetContainingMemberDeclaration(root, span.Value.Start); var endNode = service.GetContainingMemberDeclaration(root, span.Value.End); if (startNode == endNode) { // use full member span if (service.IsMethodLevelMember(startNode)) { return(startNode.FullSpan); } // use span as it is return(span); } var startSpan = service.IsMethodLevelMember(startNode) ? startNode.FullSpan : span.Value; var endSpan = service.IsMethodLevelMember(endNode) ? endNode.FullSpan : span.Value; return(TextSpan.FromBounds(Math.Min(startSpan.Start, endSpan.Start), Math.Max(startSpan.End, endSpan.End))); } #if DEBUG async Task VerifySpanBasedCompilerDiagnosticsAsync() { if (!span.HasValue) { return; } // make sure what we got from range is same as what we got from whole diagnostics var model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var rangeDeclaractionDiagnostics = model.GetDeclarationDiagnostics(span.Value).ToArray(); var rangeMethodBodyDiagnostics = model.GetMethodBodyDiagnostics(span.Value).ToArray(); var rangeDiagnostics = rangeDeclaractionDiagnostics.Concat(rangeMethodBodyDiagnostics).Where(shouldInclude).ToArray(); var wholeDeclarationDiagnostics = model.GetDeclarationDiagnostics().ToArray(); var wholeMethodBodyDiagnostics = model.GetMethodBodyDiagnostics().ToArray(); var wholeDiagnostics = wholeDeclarationDiagnostics.Concat(wholeMethodBodyDiagnostics).Where(shouldInclude).ToArray(); if (!AnalyzerHelper.AreEquivalent(rangeDiagnostics, wholeDiagnostics)) { // otherwise, report non-fatal watson so that we can fix those cases FatalError.ReportWithoutCrash(new Exception("Bug in GetDiagnostics")); // make sure we hold onto these for debugging. GC.KeepAlive(rangeDeclaractionDiagnostics); GC.KeepAlive(rangeMethodBodyDiagnostics); GC.KeepAlive(rangeDiagnostics); GC.KeepAlive(wholeDeclarationDiagnostics); GC.KeepAlive(wholeMethodBodyDiagnostics); GC.KeepAlive(wholeDiagnostics); } return;
public async Task <IEnumerable <DiagnosticData> > ComputeDiagnosticsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) { Contract.ThrowIfFalse(AnalysisScope.Analyzers.Contains(analyzer)); var document = AnalysisScope.Document; var span = AnalysisScope.Span; var kind = AnalysisScope.Kind; var loadDiagnostic = await document.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); if (analyzer == FileContentLoadAnalyzer.Instance) { return(loadDiagnostic != null? SpecializedCollections.SingletonEnumerable(DiagnosticData.Create(loadDiagnostic, document)) : SpecializedCollections.EmptyEnumerable <DiagnosticData>()); } if (loadDiagnostic != null) { return(SpecializedCollections.EmptyEnumerable <DiagnosticData>()); } if (analyzer is DocumentDiagnosticAnalyzer documentAnalyzer) { var diagnostics = await AnalyzerHelper.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( documentAnalyzer, document, kind, _compilationWithAnalyzers?.Compilation, cancellationToken).ConfigureAwait(false); return(diagnostics.ConvertToLocalDiagnostics(document, span)); } // quick optimization to reduce allocations. if (_compilationWithAnalyzers == null || !analyzer.SupportAnalysisKind(kind)) { if (kind == AnalysisKind.Syntax) { Logger.Log(FunctionId.Diagnostics_SyntaxDiagnostic, (r, d, a, k) => $"Driver: {r != null}, {d.Id}, {d.Project.Id}, {a}, {k}", _compilationWithAnalyzers, document, analyzer, kind); } return(SpecializedCollections.EmptyEnumerable <DiagnosticData>()); } // if project is not loaded successfully then, we disable semantic errors for compiler analyzers var isCompilerAnalyzer = analyzer.IsCompilerAnalyzer(); if (kind != AnalysisKind.Syntax && isCompilerAnalyzer) { var isEnabled = await document.Project.HasSuccessfullyLoadedAsync(cancellationToken).ConfigureAwait(false); Logger.Log(FunctionId.Diagnostics_SemanticDiagnostic, (a, d, e) => $"{a}, ({d.Id}, {d.Project.Id}), Enabled:{e}", analyzer, document, isEnabled); if (!isEnabled) { return(SpecializedCollections.EmptyEnumerable <DiagnosticData>()); } } var skippedAnalyzerInfo = document.Project.GetSkippedAnalyzersInfo(_analyzerInfoCache); ImmutableArray <string> filteredIds; switch (kind) { case AnalysisKind.Syntax: var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); if (tree == null) { return(SpecializedCollections.EmptyEnumerable <DiagnosticData>()); } var diagnostics = await GetSyntaxDiagnosticsAsync(tree, analyzer, isCompilerAnalyzer, cancellationToken).ConfigureAwait(false); if (diagnostics.IsDefaultOrEmpty) { Logger.Log(FunctionId.Diagnostics_SyntaxDiagnostic, (d, a, t) => $"{d.Id}, {d.Project.Id}, {a}, {t.Length}", document, analyzer, tree); } else if (skippedAnalyzerInfo.FilteredDiagnosticIdsForAnalyzers.TryGetValue(analyzer, out filteredIds)) { diagnostics = diagnostics.Filter(filteredIds); } Debug.Assert(diagnostics.Length == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, _compilationWithAnalyzers.Compilation).Count()); return(diagnostics.ConvertToLocalDiagnostics(document, span)); case AnalysisKind.Semantic: var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); if (model == null) { return(SpecializedCollections.EmptyEnumerable <DiagnosticData>()); } diagnostics = await GetSemanticDiagnosticsAsync(model, analyzer, isCompilerAnalyzer, cancellationToken).ConfigureAwait(false); if (skippedAnalyzerInfo.FilteredDiagnosticIdsForAnalyzers.TryGetValue(analyzer, out filteredIds)) { diagnostics = diagnostics.Filter(filteredIds); } Debug.Assert(diagnostics.Length == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, _compilationWithAnalyzers.Compilation).Count()); return(diagnostics.ConvertToLocalDiagnostics(document, span)); default: throw ExceptionUtilities.UnexpectedValue(kind); } }
private async Task <ImmutableArray <Diagnostic> > GetSemanticDiagnosticsAsync(SemanticModel model, DiagnosticAnalyzer analyzer, bool isCompilerAnalyzer, CancellationToken cancellationToken) { // PERF: // 1. Compute diagnostics for all analyzers with a single invocation into CompilationWithAnalyzers. // This is critical for better analyzer execution performance through re-use of bound node cache. // 2. Ensure that the compiler analyzer is treated specially and does not block on diagnostic computation // for rest of the analyzers. This is needed to ensure faster refresh for compiler diagnostics while typing. RoslynDebug.Assert(_compilationWithAnalyzers != null); var span = AnalysisScope.Span; if (isCompilerAnalyzer) { #if DEBUG VerifySpanBasedCompilerDiagnostics(model); #endif var adjustedSpan = await GetAdjustedSpanForCompilerAnalyzerAsync().ConfigureAwait(false); // TODO: Move this invocation to OOP return(await _compilationWithAnalyzers.GetAnalyzerSemanticDiagnosticsAsync(model, adjustedSpan, ImmutableArray.Create(analyzer), cancellationToken).ConfigureAwait(false)); } // We specially handle IPragmaSuppressionsAnalyzer by passing in the 'CompilationWithAnalyzers' // context to compute unnecessary pragma suppression diagnostics. // This is required because this analyzer relies on reported compiler + analyzer diagnostics // for unnecessary pragma analysis. if (analyzer is IPragmaSuppressionsAnalyzer suppressionsAnalyzer && !AnalysisScope.Span.HasValue) { using var _ = ArrayBuilder <Diagnostic> .GetInstance(out var builder); await suppressionsAnalyzer.AnalyzeAsync(model, span, _compilationWithAnalyzers, _analyzerInfoCache.GetDiagnosticDescriptors, IsCompilationEndAnalyzer, builder.Add, cancellationToken).ConfigureAwait(false); return(builder.ToImmutable()); } if (_lazySemanticDiagnostics == null) { // TODO: Move this invocation to OOP var analysisResult = await _compilationWithAnalyzers.GetAnalysisResultAsync(model, span, _compilationBasedAnalyzersInAnalysisScope, cancellationToken).ConfigureAwait(false); var treeDiagnostics = analysisResult.SemanticDiagnostics.TryGetValue(model.SyntaxTree, out var value) ? value : ImmutableDictionary <DiagnosticAnalyzer, ImmutableArray <Diagnostic> > .Empty; Interlocked.CompareExchange(ref _lazySemanticDiagnostics, treeDiagnostics, null); } return(_lazySemanticDiagnostics.TryGetValue(analyzer, out var diagnostics) ? diagnostics : ImmutableArray <Diagnostic> .Empty); bool IsCompilationEndAnalyzer(DiagnosticAnalyzer analyzer) { RoslynDebug.AssertNotNull(_compilationWithAnalyzers); return(_analyzerInfoCache.IsCompilationEndAnalyzer(analyzer, AnalysisScope.Document.Project, _compilationWithAnalyzers.Compilation) ?? true); } async Task <TextSpan?> GetAdjustedSpanForCompilerAnalyzerAsync() { // This method is to workaround a bug (https://github.com/dotnet/roslyn/issues/1557) // once that bug is fixed, we should be able to use given span as it is. Debug.Assert(isCompilerAnalyzer); if (!span.HasValue) { return(null); } var service = AnalysisScope.Document.GetRequiredLanguageService <ISyntaxFactsService>(); var root = await model.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); var startNode = service.GetContainingMemberDeclaration(root, span.Value.Start); var endNode = service.GetContainingMemberDeclaration(root, span.Value.End); if (startNode == endNode) { // use full member span if (service.IsMethodLevelMember(startNode)) { return(startNode.FullSpan); } // use span as it is return(span); } var startSpan = service.IsMethodLevelMember(startNode) ? startNode.FullSpan : span.Value; var endSpan = service.IsMethodLevelMember(endNode) ? endNode.FullSpan : span.Value; return(TextSpan.FromBounds(Math.Min(startSpan.Start, endSpan.Start), Math.Max(startSpan.End, endSpan.End))); } #if DEBUG void VerifySpanBasedCompilerDiagnostics(SemanticModel model) { if (!span.HasValue) { return; } // make sure what we got from range is same as what we got from whole diagnostics var rangeDeclaractionDiagnostics = model.GetDeclarationDiagnostics(span.Value).ToArray(); var rangeMethodBodyDiagnostics = model.GetMethodBodyDiagnostics(span.Value).ToArray(); var rangeDiagnostics = rangeDeclaractionDiagnostics.Concat(rangeMethodBodyDiagnostics).Where(shouldInclude).ToArray(); var wholeDeclarationDiagnostics = model.GetDeclarationDiagnostics().ToArray(); var wholeMethodBodyDiagnostics = model.GetMethodBodyDiagnostics().ToArray(); var wholeDiagnostics = wholeDeclarationDiagnostics.Concat(wholeMethodBodyDiagnostics).Where(shouldInclude).ToArray(); if (!AnalyzerHelper.AreEquivalent(rangeDiagnostics, wholeDiagnostics)) { // otherwise, report non-fatal watson so that we can fix those cases FatalError.ReportWithoutCrash(new Exception("Bug in GetDiagnostics")); // make sure we hold onto these for debugging. GC.KeepAlive(rangeDeclaractionDiagnostics); GC.KeepAlive(rangeMethodBodyDiagnostics); GC.KeepAlive(rangeDiagnostics); GC.KeepAlive(wholeDeclarationDiagnostics); GC.KeepAlive(wholeMethodBodyDiagnostics); GC.KeepAlive(wholeDiagnostics); } return;
/// <summary> /// Return <see cref="DiagnosticAnalyzer.SupportedDiagnostics"/> of given <paramref name="analyzer"/>. /// </summary> public ImmutableArray <DiagnosticDescriptor> GetDiagnosticDescriptors(DiagnosticAnalyzer analyzer) { var analyzerExecutor = AnalyzerHelper.GetAnalyzerExecutorForSupportedDiagnostics(analyzer, _hostDiagnosticUpdateSource); return(AnalyzerManager.Instance.GetSupportedDiagnosticDescriptors(analyzer, analyzerExecutor)); }
/// <summary> /// Return <see cref="DiagnosticAnalyzer.SupportedDiagnostics"/> of given <paramref name="analyzer"/>. /// </summary> public ImmutableArray <DiagnosticDescriptor> GetDiagnosticDescriptors(DiagnosticAnalyzer analyzer) { Func <Exception, DiagnosticAnalyzer, bool> continueOnAnalyzerException = (ex, a) => !AnalyzerHelper.IsBuiltInAnalyzer(analyzer); var analyzerExecutor = AnalyzerHelper.GetAnalyzerExecutorForSupportedDiagnostics(analyzer, _hostDiagnosticUpdateSource, continueOnAnalyzerException, CancellationToken.None); return(AnalyzerManager.Instance.GetSupportedDiagnosticDescriptors(analyzer, analyzerExecutor)); }
// virtual for testing purposes. internal virtual Action <Exception, DiagnosticAnalyzer, Diagnostic> GetOnAnalyzerException_NoTelemetryLogging(ProjectId projectId) { return((ex, analyzer, diagnostic) => AnalyzerHelper.OnAnalyzerException_NoTelemetryLogging(ex, analyzer, diagnostic, _hostDiagnosticUpdateSource, projectId)); }