Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ internal interface IDiagnosticAnalyzerService : IWorkspaceService
/// </remarks>
void RequestDiagnosticRefresh();

/// <summary>
/// Force analyzes the given project by running all applicable analyzers on the project.
/// </summary>
/// <inheritdoc cref="IRemoteDiagnosticAnalyzerService.ForceAnalyzeProjectAsync"/>
Task<ImmutableArray<DiagnosticData>> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken);

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ internal sealed partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerSer
private readonly IDiagnosticsRefresher _diagnosticsRefresher;
private readonly DiagnosticIncrementalAnalyzer _incrementalAnalyzer;
private readonly DiagnosticAnalyzerInfoCache _analyzerInfoCache;
private readonly StateManager _stateManager;

public DiagnosticAnalyzerService(
IGlobalOptionService globalOptions,
Expand All @@ -71,6 +72,7 @@ public DiagnosticAnalyzerService(
GlobalOptions = globalOptions;
_diagnosticsRefresher = diagnosticsRefresher;
_incrementalAnalyzer = new DiagnosticIncrementalAnalyzer(this, _analyzerInfoCache, this.GlobalOptions);
_stateManager = new StateManager(_analyzerInfoCache);

globalOptions.AddOptionChangedHandler(this, (_, _, e) =>
{
Expand Down Expand Up @@ -122,21 +124,102 @@ public async Task<ImmutableArray<DiagnosticData>> GetDiagnosticsForSpanAsync(
document, range, shouldIncludeDiagnostic, priorityProvider, diagnosticKinds, cancellationToken).ConfigureAwait(false);
}

public Task<ImmutableArray<DiagnosticData>> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken)
=> _incrementalAnalyzer.ForceAnalyzeProjectAsync(project, cancellationToken);
public async Task<ImmutableArray<DiagnosticData>> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken)
{
var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false);
if (client is not null)
{
var result = await client.TryInvokeAsync<IRemoteDiagnosticAnalyzerService, ImmutableArray<DiagnosticData>>(
project,
(service, solution, cancellationToken) => service.ForceAnalyzeProjectAsync(solution, project.Id, cancellationToken),
cancellationToken).ConfigureAwait(false);

return result.HasValue ? result.Value : [];
}

// No OOP connection. Compute in proc.
return await _incrementalAnalyzer.ForceAnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false);
}

public Task<ImmutableArray<DiagnosticData>> GetDiagnosticsForIdsAsync(
public async Task<ImmutableArray<DiagnosticData>> GetDiagnosticsForIdsAsync(
Project project, DocumentId? documentId, ImmutableHashSet<string>? diagnosticIds, Func<DiagnosticAnalyzer, bool>? shouldIncludeAnalyzer, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken)
{
return _incrementalAnalyzer.GetDiagnosticsForIdsAsync(project, documentId, diagnosticIds, shouldIncludeAnalyzer, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken);
var analyzers = await GetDiagnosticAnalyzersAsync(
project, diagnosticIds, shouldIncludeAnalyzer, cancellationToken).ConfigureAwait(false);

return await ProduceProjectDiagnosticsAsync(
project, analyzers, diagnosticIds,
// Ensure we compute and return diagnostics for both the normal docs and the additional docs in this
// project if no specific document id was requested.
documentId != null ? [documentId] : [.. project.DocumentIds, .. project.AdditionalDocumentIds],
includeLocalDocumentDiagnostics,
includeNonLocalDocumentDiagnostics,
// return diagnostics specific to one project or document
includeProjectNonLocalResult: documentId == null,
cancellationToken).ConfigureAwait(false);
}

public Task<ImmutableArray<DiagnosticData>> GetProjectDiagnosticsForIdsAsync(
public async Task<ImmutableArray<DiagnosticData>> GetProjectDiagnosticsForIdsAsync(
Project project, ImmutableHashSet<string>? diagnosticIds,
Func<DiagnosticAnalyzer, bool>? shouldIncludeAnalyzer,
bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken)
{
return _incrementalAnalyzer.GetProjectDiagnosticsForIdsAsync(project, diagnosticIds, shouldIncludeAnalyzer, includeNonLocalDocumentDiagnostics, cancellationToken);
var analyzers = await GetDiagnosticAnalyzersAsync(
project, diagnosticIds, shouldIncludeAnalyzer, cancellationToken).ConfigureAwait(false);

return await ProduceProjectDiagnosticsAsync(
project, analyzers, diagnosticIds,
documentIds: [],
includeLocalDocumentDiagnostics: false,
includeNonLocalDocumentDiagnostics: includeNonLocalDocumentDiagnostics,
includeProjectNonLocalResult: true,
cancellationToken).ConfigureAwait(false);
}

private Task<ImmutableArray<DiagnosticData>> ProduceProjectDiagnosticsAsync(
Project project,
ImmutableArray<DiagnosticAnalyzer> analyzers,
ImmutableHashSet<string>? diagnosticIds,
IReadOnlyList<DocumentId> documentIds,
bool includeLocalDocumentDiagnostics,
bool includeNonLocalDocumentDiagnostics,
bool includeProjectNonLocalResult,
CancellationToken cancellationToken)
{
return _incrementalAnalyzer.ProduceProjectDiagnosticsAsync(
project, analyzers, diagnosticIds, documentIds,
includeLocalDocumentDiagnostics,
includeNonLocalDocumentDiagnostics,
includeProjectNonLocalResult,
cancellationToken);
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method will now be the one that ccan call over to oop immeidately. we had to do some in-memory processing before calling it (specifically because lambdas are passed in), so we need to still do that on the host side. but none of that involves compilations, and we can now remote this call entirely over, and have the remote side call into this.


private async Task<ImmutableArray<DiagnosticAnalyzer>> GetDiagnosticAnalyzersAsync(
Project project,
ImmutableHashSet<string>? diagnosticIds,
Func<DiagnosticAnalyzer, bool>? shouldIncludeAnalyzer,
CancellationToken cancellationToken)
{
var analyzersForProject = await _stateManager.GetOrCreateAnalyzersAsync(
project.Solution.SolutionState, project.State, cancellationToken).ConfigureAwait(false);

var analyzers = analyzersForProject.WhereAsArray(a => ShouldIncludeAnalyzer(project, a));

return analyzers;

bool ShouldIncludeAnalyzer(Project project, DiagnosticAnalyzer analyzer)
{
if (!DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(analyzer, project, this.GlobalOptions))
return false;

if (shouldIncludeAnalyzer != null && !shouldIncludeAnalyzer(analyzer))
return false;

if (diagnosticIds != null && _analyzerInfoCache.GetDiagnosticDescriptors(analyzer).All(d => !diagnosticIds.Contains(d.Id)))
return false;

return true;
}
}

public async Task<ImmutableArray<DiagnosticDescriptor>> GetDiagnosticDescriptorsAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,95 +2,90 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Diagnostics;

internal sealed partial class DiagnosticAnalyzerService
{
private sealed partial class DiagnosticIncrementalAnalyzer
private sealed partial class StateManager
{
private sealed partial class StateManager
private HostAnalyzerInfo GetOrCreateHostAnalyzerInfo(
SolutionState solution, ProjectState project, ProjectAnalyzerInfo projectAnalyzerInfo)
{
private HostAnalyzerInfo GetOrCreateHostAnalyzerInfo(
SolutionState solution, ProjectState project, ProjectAnalyzerInfo projectAnalyzerInfo)
var key = new HostAnalyzerInfoKey(project.Language, project.HasSdkCodeStyleAnalyzers, solution.Analyzers.HostAnalyzerReferences);
// Some Host Analyzers may need to be treated as Project Analyzers so that they do not have access to the
// Host fallback options. These ids will be used when building up the Host and Project analyzer collections.
var referenceIdsToRedirect = GetReferenceIdsToRedirectAsProjectAnalyzers(solution, project);
var hostAnalyzerInfo = ImmutableInterlocked.GetOrAdd(ref _hostAnalyzerStateMap, key, CreateLanguageSpecificAnalyzerMap, (solution.Analyzers, referenceIdsToRedirect));
return hostAnalyzerInfo.WithExcludedAnalyzers(projectAnalyzerInfo.SkippedAnalyzersInfo.SkippedAnalyzers);

static HostAnalyzerInfo CreateLanguageSpecificAnalyzerMap(HostAnalyzerInfoKey arg, (HostDiagnosticAnalyzers HostAnalyzers, ImmutableHashSet<object> ReferenceIdsToRedirect) state)
{
var key = new HostAnalyzerInfoKey(project.Language, project.HasSdkCodeStyleAnalyzers, solution.Analyzers.HostAnalyzerReferences);
// Some Host Analyzers may need to be treated as Project Analyzers so that they do not have access to the
// Host fallback options. These ids will be used when building up the Host and Project analyzer collections.
var referenceIdsToRedirect = GetReferenceIdsToRedirectAsProjectAnalyzers(solution, project);
var hostAnalyzerInfo = ImmutableInterlocked.GetOrAdd(ref _hostAnalyzerStateMap, key, CreateLanguageSpecificAnalyzerMap, (solution.Analyzers, referenceIdsToRedirect));
return hostAnalyzerInfo.WithExcludedAnalyzers(projectAnalyzerInfo.SkippedAnalyzersInfo.SkippedAnalyzers);

static HostAnalyzerInfo CreateLanguageSpecificAnalyzerMap(HostAnalyzerInfoKey arg, (HostDiagnosticAnalyzers HostAnalyzers, ImmutableHashSet<object> ReferenceIdsToRedirect) state)
{
var language = arg.Language;
var analyzersPerReference = state.HostAnalyzers.GetOrCreateHostDiagnosticAnalyzersPerReference(language);
var language = arg.Language;
var analyzersPerReference = state.HostAnalyzers.GetOrCreateHostDiagnosticAnalyzersPerReference(language);

var (hostAnalyzerCollection, projectAnalyzerCollection) = GetAnalyzerCollections(analyzersPerReference, state.ReferenceIdsToRedirect);
var (hostAnalyzers, allAnalyzers) = PartitionAnalyzers(projectAnalyzerCollection, hostAnalyzerCollection, includeWorkspacePlaceholderAnalyzers: true);

var (hostAnalyzerCollection, projectAnalyzerCollection) = GetAnalyzerCollections(analyzersPerReference, state.ReferenceIdsToRedirect);
var (hostAnalyzers, allAnalyzers) = PartitionAnalyzers(projectAnalyzerCollection, hostAnalyzerCollection, includeWorkspacePlaceholderAnalyzers: true);
return new HostAnalyzerInfo(hostAnalyzers, allAnalyzers);
}

return new HostAnalyzerInfo(hostAnalyzers, allAnalyzers);
static (IEnumerable<ImmutableArray<DiagnosticAnalyzer>> HostAnalyzerCollection, IEnumerable<ImmutableArray<DiagnosticAnalyzer>> ProjectAnalyzerCollection) GetAnalyzerCollections(
ImmutableDictionary<object, ImmutableArray<DiagnosticAnalyzer>> analyzersPerReference,
ImmutableHashSet<object> referenceIdsToRedirectAsProjectAnalyzers)
{
if (referenceIdsToRedirectAsProjectAnalyzers.IsEmpty)
{
return (analyzersPerReference.Values, []);
}

static (IEnumerable<ImmutableArray<DiagnosticAnalyzer>> HostAnalyzerCollection, IEnumerable<ImmutableArray<DiagnosticAnalyzer>> ProjectAnalyzerCollection) GetAnalyzerCollections(
ImmutableDictionary<object, ImmutableArray<DiagnosticAnalyzer>> analyzersPerReference,
ImmutableHashSet<object> referenceIdsToRedirectAsProjectAnalyzers)
var hostAnalyzerCollection = ArrayBuilder<ImmutableArray<DiagnosticAnalyzer>>.GetInstance();
var projectAnalyzerCollection = ArrayBuilder<ImmutableArray<DiagnosticAnalyzer>>.GetInstance();

foreach (var (referenceId, analyzers) in analyzersPerReference)
{
if (referenceIdsToRedirectAsProjectAnalyzers.IsEmpty)
if (referenceIdsToRedirectAsProjectAnalyzers.Contains(referenceId))
{
return (analyzersPerReference.Values, []);
projectAnalyzerCollection.Add(analyzers);
}

var hostAnalyzerCollection = ArrayBuilder<ImmutableArray<DiagnosticAnalyzer>>.GetInstance();
var projectAnalyzerCollection = ArrayBuilder<ImmutableArray<DiagnosticAnalyzer>>.GetInstance();

foreach (var (referenceId, analyzers) in analyzersPerReference)
else
{
if (referenceIdsToRedirectAsProjectAnalyzers.Contains(referenceId))
{
projectAnalyzerCollection.Add(analyzers);
}
else
{
hostAnalyzerCollection.Add(analyzers);
}
hostAnalyzerCollection.Add(analyzers);
}

return (hostAnalyzerCollection.ToImmutableAndFree(), projectAnalyzerCollection.ToImmutableAndFree());
}

return (hostAnalyzerCollection.ToImmutableAndFree(), projectAnalyzerCollection.ToImmutableAndFree());
}
}

private static ImmutableHashSet<object> GetReferenceIdsToRedirectAsProjectAnalyzers(
SolutionState solution, ProjectState project)
private static ImmutableHashSet<object> GetReferenceIdsToRedirectAsProjectAnalyzers(
SolutionState solution, ProjectState project)
{
if (project.HasSdkCodeStyleAnalyzers)
{
if (project.HasSdkCodeStyleAnalyzers)
{
// When a project uses CodeStyle analyzers added by the SDK, we remove them in favor of the
// Features analyzers. We need to then treat the Features analyzers as Project analyzers so
// they do not get access to the Host fallback options.
return GetFeaturesAnalyzerReferenceIds(solution.Analyzers);
}

return [];
// When a project uses CodeStyle analyzers added by the SDK, we remove them in favor of the
// Features analyzers. We need to then treat the Features analyzers as Project analyzers so
// they do not get access to the Host fallback options.
return GetFeaturesAnalyzerReferenceIds(solution.Analyzers);
}

static ImmutableHashSet<object> GetFeaturesAnalyzerReferenceIds(HostDiagnosticAnalyzers hostAnalyzers)
{
var builder = ImmutableHashSet.CreateBuilder<object>();
return [];

foreach (var analyzerReference in hostAnalyzers.HostAnalyzerReferences)
{
if (analyzerReference.IsFeaturesAnalyzer())
builder.Add(analyzerReference.Id);
}
static ImmutableHashSet<object> GetFeaturesAnalyzerReferenceIds(HostDiagnosticAnalyzers hostAnalyzers)
{
var builder = ImmutableHashSet.CreateBuilder<object>();

return builder.ToImmutable();
foreach (var analyzerReference in hostAnalyzers.HostAnalyzerReferences)
{
if (analyzerReference.IsFeaturesAnalyzer())
builder.Add(analyzerReference.Id);
}

return builder.ToImmutable();
}
}
}
Expand Down
Loading
Loading