Skip to content

Commit

Permalink
Decouple FixAll from the worker by introducing bypass methods to anal…
Browse files Browse the repository at this point in the history
…yze a specific document directly and skip the cache.
  • Loading branch information
333fred committed Oct 14, 2020
1 parent c610d51 commit 5cc02a9
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ internal static DiagnosticLocation ToDiagnosticLocation(this Diagnostic diagnost
internal static IEnumerable<DiagnosticLocation> DistinctDiagnosticLocationsByProject(this IEnumerable<DocumentDiagnostics> documentDiagnostic)
{
return documentDiagnostic
.SelectMany(x => x.Diagnostics, (parent, child) => (projectName: parent.Project.Name, diagnostic: child))
.SelectMany(x => x.Diagnostics, (parent, child) => (projectName: parent.ProjectName, diagnostic: child))
.Select(x => new
{
location = x.diagnostic.ToDiagnosticLocation(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ private static QuickFixResponse GetResponseFromDiagnostics(ImmutableArray<Docume
{
var diagnosticLocations = diagnostics
.Where(x => string.IsNullOrEmpty(fileName)
|| x.Document.FilePath == fileName)
|| x.DocumentPath == fileName)
.DistinctDiagnosticLocationsByProject()
.Where(x => x.FileName != null);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public override async Task<GetFixAllResponse> Handle(GetFixAllRequest request)

var allDiagnostics = await GetDiagnosticsAsync(request.Scope, document);
var validFixes = allDiagnostics
.GroupBy(docAndDiag => docAndDiag.Document.Project)
.GroupBy(docAndDiag => docAndDiag.ProjectId)
.SelectMany(grouping =>
{
var projectFixProviders = GetCodeFixProviders(grouping.Key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,26 +192,13 @@ public FixAllDiagnosticProvider(ICsDiagnosticWorker diagnosticWorker)
}

public override async Task<IEnumerable<Diagnostic>> GetAllDiagnosticsAsync(Project project, CancellationToken cancellationToken)
{
var diagnostics = await _diagnosticWorker.GetDiagnostics(project.Documents.ToImmutableArray());
return diagnostics.SelectMany(x => x.Diagnostics);
}
=> await _diagnosticWorker.AnalyzeProjectsAsync(project, cancellationToken);

public override async Task<IEnumerable<Diagnostic>> GetDocumentDiagnosticsAsync(Document document, CancellationToken cancellationToken)
{
var documentDiagnostics = await _diagnosticWorker.GetDiagnostics(ImmutableArray.Create(document));

if (!documentDiagnostics.Any())
return new Diagnostic[] { };

return documentDiagnostics.First().Diagnostics;
}
=> await _diagnosticWorker.AnalyzeDocumentAsync(document, cancellationToken);

public override async Task<IEnumerable<Diagnostic>> GetProjectDiagnosticsAsync(Project project, CancellationToken cancellationToken)
{
var diagnostics = await _diagnosticWorker.GetDiagnostics(project.Documents.ToImmutableArray());
return diagnostics.SelectMany(x => x.Diagnostics);
}
=> await _diagnosticWorker.AnalyzeProjectsAsync(project, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ private TextSpan GetTextSpan(ICodeActionRequest request, SourceText sourceText)

private async Task CollectCodeFixesActions(Document document, TextSpan span, List<CodeAction> codeActions)
{
var diagnosticsWithProjects = await _diagnostics.GetDiagnostics(ImmutableArray.Create(document));
var diagnosticsWithProjects = await _diagnostics.GetDiagnostics(ImmutableArray.Create(document.FilePath));

var groupedBySpan = diagnosticsWithProjects
.SelectMany(x => x.Diagnostics)
Expand Down Expand Up @@ -167,7 +167,7 @@ private async Task AppendFixesAsync(Document document, TextSpan span, IEnumerabl

private List<CodeFixProvider> GetSortedCodeFixProviders(Document document)
{
return ExtensionOrderer.GetOrderedOrUnorderedList<CodeFixProvider, ExportCodeFixProviderAttribute>(_codeFixesForProject.GetAllCodeFixesForProject(document.Project), attribute => attribute.Name).ToList();
return ExtensionOrderer.GetOrderedOrUnorderedList<CodeFixProvider, ExportCodeFixProviderAttribute>(_codeFixesForProject.GetAllCodeFixesForProject(document.Project.Id), attribute => attribute.Name).ToList();
}

private List<CodeRefactoringProvider> GetSortedCodeRefactoringProviders()
Expand Down Expand Up @@ -223,14 +223,14 @@ private IEnumerable<AvailableCodeAction> ConvertToAvailableCodeAction(IEnumerabl
{
if (documentAndDiagnostics.Diagnostics.FirstOrDefault(d => d.Id == diagnosticId) is Diagnostic diagnostic)
{
return (documentAndDiagnostics.Document.Id, diagnostic);
return (documentAndDiagnostics.DocumentId, diagnostic);
}
}

return default;
}

protected ImmutableArray<CodeFixProvider> GetCodeFixProviders(Project project)
protected ImmutableArray<CodeFixProvider> GetCodeFixProviders(ProjectId project)
{
return _codeFixesForProject.GetAllCodeFixesForProject(project);
}
Expand All @@ -239,21 +239,21 @@ protected ImmutableArray<CodeFixProvider> GetCodeFixProviders(Project project)
{
// If Roslyn ever comes up with a UI for selecting what provider the user prefers, we might consider replicating.
// https://github.com/dotnet/roslyn/issues/27066
return _codeFixesForProject.GetAllCodeFixesForProject(document.Project).FirstOrDefault(provider => provider.HasFixForId(id));
return _codeFixesForProject.GetAllCodeFixesForProject(document.Project.Id).FirstOrDefault(provider => provider.HasFixForId(id));
}

protected async Task<ImmutableArray<DocumentDiagnostics>> GetDiagnosticsAsync(FixAllScope scope, Document document)
{
switch (scope)
{
case FixAllScope.Solution:
var documentsInSolution = document.Project.Solution.Projects.SelectMany(p => p.Documents).ToImmutableArray();
var documentsInSolution = document.Project.Solution.Projects.SelectMany(p => p.Documents).Select(d => d.FilePath).ToImmutableArray();
return await _diagnostics.GetDiagnostics(documentsInSolution);
case FixAllScope.Project:
var documentsInProject = document.Project.Documents.ToImmutableArray();
var documentsInProject = document.Project.Documents.Select(d => d.FilePath).ToImmutableArray();
return await _diagnostics.GetDiagnostics(documentsInProject);
case FixAllScope.Document:
return await _diagnostics.GetDiagnostics(ImmutableArray.Create(document));
return await _diagnostics.GetDiagnostics(ImmutableArray.Create(document.FilePath));
default:
throw new InvalidOperationException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -201,5 +202,23 @@ public void Dispose()
_workspace.DocumentClosed -= OnDocumentOpened;
_disposable.Dispose();
}

public async Task<IEnumerable<Diagnostic>> AnalyzeDocumentAsync(Document document, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
return await GetDiagnosticsForDocument(document, document.Project.Name);
}

public async Task<IEnumerable<Diagnostic>> AnalyzeProjectsAsync(Project project, CancellationToken cancellationToken)
{
var diagnostics = new List<Diagnostic>();
foreach (var document in project.Documents)
{
cancellationToken.ThrowIfCancellationRequested();
diagnostics.AddRange(await GetDiagnosticsForDocument(document, project.Name));
}

return diagnostics;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,42 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEv
}
}

public async Task<IEnumerable<Diagnostic>> AnalyzeDocumentAsync(Document document, CancellationToken cancellationToken)
{
Project project = document.Project;
var allAnalyzers = _providers
.SelectMany(x => x.CodeDiagnosticAnalyzerProviders)
.Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzers(project.Language)))
.ToImmutableArray();

var compilation = await project.GetCompilationAsync();
var workspaceAnalyzerOptions = (AnalyzerOptions)_workspaceAnalyzerOptionsConstructor.Invoke(new object[] { project.AnalyzerOptions, project.Solution });

cancellationToken.ThrowIfCancellationRequested();
return await AnalyzeDocument(project, allAnalyzers, compilation, workspaceAnalyzerOptions, document);
}

public async Task<IEnumerable<Diagnostic>> AnalyzeProjectsAsync(Project project, CancellationToken cancellationToken)
{
var diagnostics = new List<Diagnostic>();
var allAnalyzers = _providers
.SelectMany(x => x.CodeDiagnosticAnalyzerProviders)
.Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzers(project.Language)))
.ToImmutableArray();

var compilation = await project.GetCompilationAsync();
var workspaceAnalyzerOptions = (AnalyzerOptions)_workspaceAnalyzerOptionsConstructor.Invoke(new object[] { project.AnalyzerOptions, project.Solution });


foreach (var document in project.Documents)
{
cancellationToken.ThrowIfCancellationRequested();
diagnostics.AddRange(await AnalyzeDocument(project, allAnalyzers, compilation, workspaceAnalyzerOptions, document));
}

return diagnostics;
}

private async Task AnalyzeProject(Solution solution, IGrouping<ProjectId, DocumentId> documentsGroupedByProject)
{
try
Expand All @@ -200,15 +236,15 @@ private async Task AnalyzeProject(Solution solution, IGrouping<ProjectId, Docume
.Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzers(project.Language)))
.ToImmutableArray();

var compiled = await project
.GetCompilationAsync();
var compilation = await project.GetCompilationAsync();

var workspaceAnalyzerOptions = (AnalyzerOptions) _workspaceAnalyzerOptionsConstructor.Invoke(new object[] {project.AnalyzerOptions, project.Solution});
var workspaceAnalyzerOptions = (AnalyzerOptions)_workspaceAnalyzerOptionsConstructor.Invoke(new object[] { project.AnalyzerOptions, project.Solution });

foreach (var documentId in documentsGroupedByProject)
{
var document = project.GetDocument(documentId);
await AnalyzeDocument(project, allAnalyzers, compiled, workspaceAnalyzerOptions, document);
var diagnostics = await AnalyzeDocument(project, allAnalyzers, compilation, workspaceAnalyzerOptions, document);
UpdateCurrentDiagnostics(project, document, diagnostics);
}
}
catch (Exception ex)
Expand All @@ -217,7 +253,7 @@ private async Task AnalyzeProject(Solution solution, IGrouping<ProjectId, Docume
}
}

private async Task AnalyzeDocument(Project project, ImmutableArray<DiagnosticAnalyzer> allAnalyzers, Compilation compiled, AnalyzerOptions workspaceAnalyzerOptions, Document document)
private async Task<ImmutableArray<Diagnostic>> AnalyzeDocument(Project project, ImmutableArray<DiagnosticAnalyzer> allAnalyzers, Compilation compilation, AnalyzerOptions workspaceAnalyzerOptions, Document document)
{
try
{
Expand All @@ -238,7 +274,7 @@ private async Task AnalyzeDocument(Project project, ImmutableArray<DiagnosticAna
}
else if (allAnalyzers.Any()) // Analyzers cannot be called with empty analyzer list.
{
var compilationWithAnalyzers = compiled.WithAnalyzers(allAnalyzers, new CompilationWithAnalyzersOptions(
var compilationWithAnalyzers = compilation.WithAnalyzers(allAnalyzers, new CompilationWithAnalyzersOptions(
workspaceAnalyzerOptions,
onAnalyzerException: OnAnalyzerException,
concurrentAnalysis: false,
Expand All @@ -258,14 +294,15 @@ private async Task AnalyzeDocument(Project project, ImmutableArray<DiagnosticAna
}
else
{
diagnostics = documentSemanticModel.GetDiagnostics().ToImmutableArray();
diagnostics = documentSemanticModel.GetDiagnostics();
}

UpdateCurrentDiagnostics(project, document, diagnostics);
return diagnostics;
}
catch (Exception ex)
{
_logger.LogError($"Analysis of document {document.Name} failed or cancelled by timeout: {ex.Message}, analysers: {string.Join(", ", allAnalyzers)}");
return ImmutableArray<Diagnostic>.Empty;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,15 @@ public void Dispose()
if (_implementation is IDisposable disposable) disposable.Dispose();
_onChange.Dispose();
}

public Task<IEnumerable<Diagnostic>> AnalyzeDocumentAsync(Document document, CancellationToken cancellationToken)
{
return _implementation.AnalyzeDocumentAsync(document, cancellationToken);
}

public Task<IEnumerable<Diagnostic>> AnalyzeProjectsAsync(Project project, CancellationToken cancellationToken)
{
return _implementation.AnalyzeProjectsAsync(project, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using OmniSharp.Roslyn.CSharp.Services.Diagnostics;
Expand All @@ -9,7 +11,9 @@ public interface ICsDiagnosticWorker
{
Task<ImmutableArray<DocumentDiagnostics>> GetDiagnostics(ImmutableArray<string> documentPaths);
Task<ImmutableArray<DocumentDiagnostics>> GetAllDiagnosticsAsync();
Task<IEnumerable<Diagnostic>> AnalyzeDocumentAsync(Document document, CancellationToken cancellationToken);
Task<IEnumerable<Diagnostic>> AnalyzeProjectsAsync(Project project, CancellationToken cancellationToken);
ImmutableArray<DocumentId> QueueDocumentsForDiagnostics();
ImmutableArray<DocumentId> QueueDocumentsForDiagnostics(ImmutableArray<ProjectId> projectId);
}
}
}

0 comments on commit 5cc02a9

Please sign in to comment.