public async Task<FirstDiagnosticResult> GetMostSevereFixableDiagnosticAsync( Document document, TextSpan range, CancellationToken cancellationToken) { if (document == null || !document.IsOpen()) { return default; } using var diagnostics = SharedPools.Default<List<DiagnosticData>>().GetPooledObject(); using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var linkedToken = linkedTokenSource.Token; // This flag is used by SuggestedActionsSource to track what solution is was // last able to get "full results" for. var isFullResult = await _diagnosticService.TryAppendDiagnosticsForSpanAsync( document, range, diagnostics.Object, cancellationToken: linkedToken).ConfigureAwait(false); var errorDiagnostics = diagnostics.Object.Where(d => d.Severity == DiagnosticSeverity.Error); var otherDiagnostics = diagnostics.Object.Where(d => d.Severity != DiagnosticSeverity.Error); // Kick off a task that will determine there's an Error Diagnostic with a fixer var errorDiagnosticsTask = Task.Run( () => GetFirstDiagnosticWithFixAsync(document, errorDiagnostics, range, linkedToken), linkedToken); // Kick off a task that will determine if any non-Error Diagnostic has a fixer var otherDiagnosticsTask = Task.Run( () => GetFirstDiagnosticWithFixAsync(document, otherDiagnostics, range, linkedToken), linkedToken); // If the error diagnostics task happens to complete with a non-null result before // the other diagnostics task, we can cancel the other task. var diagnostic = await errorDiagnosticsTask.ConfigureAwait(false) ?? await otherDiagnosticsTask.ConfigureAwait(false); linkedTokenSource.Cancel(); return new FirstDiagnosticResult(partialResult: !isFullResult, hasFix: diagnostic != null, diagnostic: diagnostic); }
public async Task <FirstDiagnosticResult> GetFirstDiagnosticWithFixAsync( Document document, TextSpan range, CancellationToken cancellationToken) { if (document == null || !document.IsOpen()) { return(default(FirstDiagnosticResult)); } using (var diagnostics = SharedPools.Default <List <DiagnosticData> >().GetPooledObject()) { var fullResult = await _diagnosticService.TryAppendDiagnosticsForSpanAsync(document, range, diagnostics.Object, cancellationToken : cancellationToken).ConfigureAwait(false); foreach (var diagnostic in diagnostics.Object) { cancellationToken.ThrowIfCancellationRequested(); if (!range.IntersectsWith(diagnostic.TextSpan)) { continue; } // REVIEW: 2 possible designs. // 1. find the first fix and then return right away. if the lightbulb is actually expanded, find all fixes for the line synchronously. or // 2. kick off a task that finds all fixes for the given range here but return once we find the first one. // at the same time, let the task to run to finish. if the lightbulb is expanded, we just simply use the task to get all fixes. // // first approach is simpler, so I will implement that first. if the first approach turns out to be not good enough, then // I will try the second approach which will be more complex but quicker var hasFix = await ContainsAnyFix(document, diagnostic, cancellationToken).ConfigureAwait(false); if (hasFix) { return(new FirstDiagnosticResult(!fullResult, hasFix, diagnostic)); } } return(new FirstDiagnosticResult(!fullResult, false, default(DiagnosticData))); } }