diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs index cc31be1188cb3..c4f33d130bbb8 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs @@ -656,28 +656,21 @@ await TestNuGetAndVsixAnalyzerCoreAsync( // 1) No duplicate diagnostics // 2) Both NuGet and Vsix analyzers execute // 3) Appropriate diagnostic filtering is done - Nuget suppressor suppresses VSIX analyzer. - // - // 🐛 After splitting fallback options into separate CompilationWithAnalyzers for project and host analyzers, - // NuGet-installed suppressors no longer act on VSIX-installed analyzer diagnostics. Fixing this requires us to - // add NuGet-installed analyzer references to the host CompilationWithAnalyzers, with an additional flag - // indicating that only suppressors should run for these references. - // https://github.com/dotnet/roslyn/issues/75399 - const bool FalseButShouldBeTrue = false; await TestNuGetAndVsixAnalyzerCoreAsync( nugetAnalyzers: ImmutableArray.Create(firstNugetAnalyzer), expectedNugetAnalyzersExecuted: true, vsixAnalyzers: ImmutableArray.Create(vsixAnalyzer), expectedVsixAnalyzersExecuted: true, nugetSuppressors: ImmutableArray.Create(nugetSuppressor), - expectedNugetSuppressorsExecuted: FalseButShouldBeTrue, + expectedNugetSuppressorsExecuted: true, vsixSuppressors: ImmutableArray.Empty, expectedVsixSuppressorsExecuted: false, new[] { (Diagnostic("A", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)), - (Diagnostic("X", "Class", isSuppressed: FalseButShouldBeTrue).WithLocation(1, 7), nameof(VsixAnalyzer)), - (Diagnostic("Y", "Class", isSuppressed: FalseButShouldBeTrue).WithLocation(1, 7), nameof(VsixAnalyzer)), - (Diagnostic("Z", "Class", isSuppressed: FalseButShouldBeTrue).WithLocation(1, 7), nameof(VsixAnalyzer)) + (Diagnostic("X", "Class", isSuppressed: true).WithLocation(1, 7), nameof(VsixAnalyzer)), + (Diagnostic("Y", "Class", isSuppressed: true).WithLocation(1, 7), nameof(VsixAnalyzer)), + (Diagnostic("Z", "Class", isSuppressed: true).WithLocation(1, 7), nameof(VsixAnalyzer)) }); // Suppressors with duplicate support for VsixAnalyzer, but not 100% overlap. Verify the following: @@ -691,15 +684,15 @@ await TestNuGetAndVsixAnalyzerCoreAsync( vsixAnalyzers: ImmutableArray.Create(vsixAnalyzer), expectedVsixAnalyzersExecuted: true, nugetSuppressors: ImmutableArray.Create(partialNugetSuppressor), - expectedNugetSuppressorsExecuted: FalseButShouldBeTrue, + expectedNugetSuppressorsExecuted: true, vsixSuppressors: ImmutableArray.Create(vsixSuppressor), expectedVsixSuppressorsExecuted: false, new[] { (Diagnostic("A", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)), (Diagnostic("X", "Class", isSuppressed: false).WithLocation(1, 7), nameof(VsixAnalyzer)), - (Diagnostic("Y", "Class", isSuppressed: FalseButShouldBeTrue).WithLocation(1, 7), nameof(VsixAnalyzer)), - (Diagnostic("Z", "Class", isSuppressed: FalseButShouldBeTrue).WithLocation(1, 7), nameof(VsixAnalyzer)) + (Diagnostic("Y", "Class", isSuppressed: true).WithLocation(1, 7), nameof(VsixAnalyzer)), + (Diagnostic("Z", "Class", isSuppressed: true).WithLocation(1, 7), nameof(VsixAnalyzer)) }); // Suppressors with duplicate support for VsixAnalyzer, with 100% overlap. Verify the following: @@ -713,15 +706,15 @@ await TestNuGetAndVsixAnalyzerCoreAsync( vsixAnalyzers: ImmutableArray.Create(vsixAnalyzer), expectedVsixAnalyzersExecuted: true, nugetSuppressors: ImmutableArray.Create(nugetSuppressor), - expectedNugetSuppressorsExecuted: FalseButShouldBeTrue, + expectedNugetSuppressorsExecuted: true, vsixSuppressors: ImmutableArray.Create(vsixSuppressor), expectedVsixSuppressorsExecuted: false, new[] { (Diagnostic("A", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)), - (Diagnostic("X", "Class", isSuppressed: FalseButShouldBeTrue).WithLocation(1, 7), nameof(VsixAnalyzer)), - (Diagnostic("Y", "Class", isSuppressed: FalseButShouldBeTrue).WithLocation(1, 7), nameof(VsixAnalyzer)), - (Diagnostic("Z", "Class", isSuppressed: FalseButShouldBeTrue).WithLocation(1, 7), nameof(VsixAnalyzer)) + (Diagnostic("X", "Class", isSuppressed: true).WithLocation(1, 7), nameof(VsixAnalyzer)), + (Diagnostic("Y", "Class", isSuppressed: true).WithLocation(1, 7), nameof(VsixAnalyzer)), + (Diagnostic("Z", "Class", isSuppressed: true).WithLocation(1, 7), nameof(VsixAnalyzer)) }); } diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor_Helpers.cs b/src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor_Helpers.cs index 12b8d9ceac862..41dade51c947f 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor_Helpers.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor_Helpers.cs @@ -146,6 +146,8 @@ static string GetLanguageSpecificId(string? language, string noLanguageId, strin // 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()); + var filteredProjectSuppressors = filteredProjectAnalyzers.WhereAsArray(static a => a is DiagnosticSuppressor); + filteredHostAnalyzers = filteredHostAnalyzers.AddRange(filteredProjectSuppressors); // PERF: there is no analyzers for this compilation. // compilationWithAnalyzer will throw if it is created with no analyzers which is perf optimization. diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs index 9523e3dd6c3d8..efc68d8751622 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs +++ b/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs @@ -167,7 +167,9 @@ private async Task projectAnalyzers, ImmutableAr } } - return (projectBuilder.ToImmutableAndClear(), hostBuilder.ToImmutableAndClear()); + var projectAnalyzers = projectBuilder.ToImmutableAndClear(); + + if (hostAnalyzerIds.Any()) + { + // If any host analyzers are active, make sure to also include any project diagnostic suppressors + hostBuilder.AddRange(projectAnalyzers.WhereAsArray(static a => a is DiagnosticSuppressor)); + } + + return (projectAnalyzers, hostBuilder.ToImmutableAndClear()); } private async Task<(CompilationWithAnalyzersPair? compilationWithAnalyzers, BidirectionalMap analyzerToIdMap)> GetOrCreateCompilationWithAnalyzersAsync(CancellationToken cancellationToken) @@ -609,6 +617,7 @@ private async Task CreateCompilationWithAnal var analyzers = reference.GetAnalyzers(_project.Language); projectAnalyzerBuilder.AddRange(analyzers); + hostAnalyzerBuilder.AddRange(analyzers.WhereAsArray(static a => a is DiagnosticSuppressor)); analyzerMapBuilder.AppendAnalyzerMap(analyzers); }