diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs
index 09efb8642d42b..39cf98993dda9 100644
--- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs
+++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs
@@ -18,13 +18,13 @@ namespace Microsoft.CodeAnalysis.Diagnostics;
internal partial class DiagnosticAnalyzerService
{
///
- /// Cached data from a to the last instance created
- /// for it. Note: the CompilationWithAnalyzersPair instance is dependent on the set of to the last instance
+ /// created for it. Note: the CompilationWithAnalyzersPair instance is dependent on the set of s passed along with the project. As such, we might not be able to use a prior cached
/// value if the set of analyzers changes. In that case, a new instance will be created and will be cached for the
/// next caller.
///
- private static readonly ConditionalWeakTable analyzers, CompilationWithAnalyzersPair? compilationWithAnalyzersPair)>> s_projectToCompilationWithAnalyzers = new();
+ private static readonly ConditionalWeakTable analyzers, CompilationWithAnalyzersPair? compilationWithAnalyzersPair)>> s_projectToCompilationWithAnalyzers = new();
private static async Task GetOrCreateCompilationWithAnalyzersAsync(
Project project,
@@ -36,25 +36,29 @@ internal partial class DiagnosticAnalyzerService
if (!project.SupportsCompilation)
return null;
+ var projectState = project.State;
+ var checksum = await project.GetDependentChecksumAsync(cancellationToken).ConfigureAwait(false);
+
// Make sure the cached pair was computed with at least the same state sets we're asking about. if not,
// recompute and cache with the new state sets.
- if (!s_projectToCompilationWithAnalyzers.TryGetValue(project, out var tupleBox) ||
+ if (!s_projectToCompilationWithAnalyzers.TryGetValue(projectState, out var tupleBox) ||
!analyzers.IsSubsetOf(tupleBox.Value.analyzers))
{
- var compilationWithAnalyzersPair = await CreateCompilationWithAnalyzersAsync().ConfigureAwait(false);
- tupleBox = new((analyzers, compilationWithAnalyzersPair));
+ var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
+ var compilationWithAnalyzersPair = CreateCompilationWithAnalyzers(projectState, compilation);
+ tupleBox = new((checksum, analyzers, compilationWithAnalyzersPair));
#if NET
- s_projectToCompilationWithAnalyzers.AddOrUpdate(project, tupleBox);
+ s_projectToCompilationWithAnalyzers.AddOrUpdate(projectState, tupleBox);
#else
// Make a best effort attempt to store the latest computed value against these state sets. If this
// fails (because another thread interleaves with this), that's ok. We still return the pair we
// computed, so our caller will still see the right data
- s_projectToCompilationWithAnalyzers.Remove(project);
+ s_projectToCompilationWithAnalyzers.Remove(projectState);
// Intentionally ignore the result of this. We still want to use the value we computed above, even if
// another thread interleaves and sets a different value.
- s_projectToCompilationWithAnalyzers.GetValue(project, _ => tupleBox);
+ s_projectToCompilationWithAnalyzers.GetValue(projectState, _ => tupleBox);
#endif
}
@@ -63,13 +67,12 @@ internal partial class DiagnosticAnalyzerService
//
// Should only be called on a that .
//
- async Task CreateCompilationWithAnalyzersAsync()
+ CompilationWithAnalyzersPair? CreateCompilationWithAnalyzers(
+ ProjectState project, Compilation compilation)
{
var projectAnalyzers = analyzers.WhereAsArray(static (s, info) => !info.IsHostAnalyzer(s), hostAnalyzerInfo);
var hostAnalyzers = analyzers.WhereAsArray(static (s, info) => info.IsHostAnalyzer(s), hostAnalyzerInfo);
- var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
-
// Create driver that holds onto compilation and associated analyzers
var filteredProjectAnalyzers = projectAnalyzers.WhereAsArray(static a => !a.IsWorkspaceDiagnosticAnalyzer());
var filteredHostAnalyzers = hostAnalyzers.WhereAsArray(static a => !a.IsWorkspaceDiagnosticAnalyzer());
@@ -83,9 +86,6 @@ internal partial class DiagnosticAnalyzerService
return null;
}
- Contract.ThrowIfFalse(project.SupportsCompilation);
- AssertCompilation(project, compilation);
-
var exceptionFilter = (Exception ex) =>
{
if (ex is not OperationCanceledException && crashOnAnalyzerException)
@@ -105,7 +105,7 @@ internal partial class DiagnosticAnalyzerService
var projectCompilation = !filteredProjectAnalyzers.Any()
? null
: compilation.WithAnalyzers(filteredProjectAnalyzers, new CompilationWithAnalyzersOptions(
- options: project.AnalyzerOptions,
+ options: project.ProjectAnalyzerOptions,
onAnalyzerException: null,
analyzerExceptionFilter: exceptionFilter,
concurrentAnalysis: false,
@@ -126,12 +126,4 @@ internal partial class DiagnosticAnalyzerService
return new CompilationWithAnalyzersPair(projectCompilation, hostCompilation);
}
}
-
- [Conditional("DEBUG")]
- private static void AssertCompilation(Project project, Compilation compilation1)
- {
- // given compilation must be from given project.
- Contract.ThrowIfFalse(project.TryGetCompilation(out var compilation2));
- Contract.ThrowIfFalse(compilation1 == compilation2);
- }
}
diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs
index 292ac0a5d21d9..9a917b7ff7ef5 100644
--- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs
+++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs
@@ -17,13 +17,14 @@ private partial class DiagnosticIncrementalAnalyzer
{
private partial class StateManager
{
- private HostAnalyzerInfo GetOrCreateHostAnalyzerInfo(Project project, ProjectAnalyzerInfo projectAnalyzerInfo)
+ private HostAnalyzerInfo GetOrCreateHostAnalyzerInfo(
+ SolutionState solution, ProjectState project, ProjectAnalyzerInfo projectAnalyzerInfo)
{
- var key = new HostAnalyzerInfoKey(project.Language, project.State.HasSdkCodeStyleAnalyzers, project.Solution.SolutionState.Analyzers.HostAnalyzerReferences);
+ 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(project);
- var hostAnalyzerInfo = ImmutableInterlocked.GetOrAdd(ref _hostAnalyzerStateMap, key, CreateLanguageSpecificAnalyzerMap, (project.Solution.SolutionState.Analyzers, referenceIdsToRedirect));
+ 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