File: Diagnostics\DiagnosticAnalyzerDriver\DiagnosticAnalyzerDriverTests.cs
Web Access
Project: src\src\EditorFeatures\CSharpTest\Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// 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 System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.UnitTests;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.UnitTests.Diagnostics;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
using static Roslyn.Test.Utilities.TestBase;
 
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics.UserDiagnosticProviderEngine;
 
[UseExportProvider]
public class DiagnosticAnalyzerDriverTests
{
    private static readonly TestComposition s_compositionWithMockDiagnosticUpdateSourceRegistrationService = EditorTestCompositions.EditorFeatures;
 
    [Fact]
    public async Task DiagnosticAnalyzerDriverAllInOne()
    {
        var source = TestResource.AllInOneCSharpCode;
 
        // AllInOneCSharpCode has no properties with initializers or named types with primary constructors.
        var symbolKindsWithNoCodeBlocks = new HashSet<SymbolKind>
        {
            SymbolKind.Property,
            SymbolKind.NamedType
        };
 
        var missingSyntaxNodes = new HashSet<SyntaxKind>
        {
            // https://github.com/dotnet/roslyn/issues/44682 - Add to all in one
            SyntaxKind.WithExpression,
            SyntaxKind.RecordDeclaration,
            SyntaxKind.CollectionExpression,
            SyntaxKind.ExpressionElement,
            SyntaxKind.SpreadElement
        };
 
        var analyzer = new CSharpTrackingDiagnosticAnalyzer();
        using var workspace = EditorTestWorkspace.CreateCSharp(source, TestOptions.Regular, composition: s_compositionWithMockDiagnosticUpdateSourceRegistrationService);
 
        var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create<DiagnosticAnalyzer>(analyzer));
        var newSolution = workspace.CurrentSolution.WithAnalyzerReferences([analyzerReference])
            .Projects.Single().AddAdditionalDocument(name: "dummy.txt", text: "", filePath: "dummy.txt").Project.Solution;
        workspace.TryApplyChanges(newSolution);
 
        var document = workspace.CurrentSolution.Projects.Single().Documents.Single();
        AccessSupportedDiagnostics(analyzer);
        await DiagnosticProviderTestUtilities.GetAllDiagnosticsAsync(workspace, document, new TextSpan(0, document.GetTextAsync().Result.Length));
        analyzer.VerifyAllAnalyzerMembersWereCalled();
        analyzer.VerifyAnalyzeSymbolCalledForAllSymbolKinds();
        analyzer.VerifyAnalyzeNodeCalledForAllSyntaxKinds(missingSyntaxNodes);
        analyzer.VerifyOnCodeBlockCalledForAllSymbolAndMethodKinds(symbolKindsWithNoCodeBlocks, true);
    }
 
    [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/908658")]
    public async Task DiagnosticAnalyzerDriverVsAnalyzerDriverOnCodeBlock()
    {
        var methodNames = new string[] { "Initialize", "AnalyzeCodeBlock" };
        var source = """
            [System.Obsolete]
            class C
            {
                int P { get; set; }
                delegate void A();
                delegate string F();
            }
            """;
 
        var ideEngineAnalyzer = new CSharpTrackingDiagnosticAnalyzer();
        using (var ideEngineWorkspace = EditorTestWorkspace.CreateCSharp(source, composition: s_compositionWithMockDiagnosticUpdateSourceRegistrationService))
        {
            var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create<DiagnosticAnalyzer>(ideEngineAnalyzer));
            ideEngineWorkspace.TryApplyChanges(ideEngineWorkspace.CurrentSolution.WithAnalyzerReferences([analyzerReference]));
 
            var ideEngineDocument = ideEngineWorkspace.CurrentSolution.Projects.Single().Documents.Single();
            await DiagnosticProviderTestUtilities.GetAllDiagnosticsAsync(ideEngineWorkspace, ideEngineDocument, new TextSpan(0, ideEngineDocument.GetTextAsync().Result.Length));
            foreach (var method in methodNames)
            {
                Assert.False(ideEngineAnalyzer.CallLog.Any(e => e.CallerName == method && e.MethodKind == MethodKind.DelegateInvoke && e.ReturnsVoid));
                Assert.False(ideEngineAnalyzer.CallLog.Any(e => e.CallerName == method && e.MethodKind == MethodKind.DelegateInvoke && !e.ReturnsVoid));
                Assert.True(ideEngineAnalyzer.CallLog.Any(e => e.CallerName == method && e.SymbolKind == SymbolKind.NamedType));
                Assert.False(ideEngineAnalyzer.CallLog.Any(e => e.CallerName == method && e.SymbolKind == SymbolKind.Property));
            }
        }
 
        var compilerEngineAnalyzer = new CSharpTrackingDiagnosticAnalyzer();
        using var compilerEngineWorkspace = EditorTestWorkspace.CreateCSharp(source, composition: s_compositionWithMockDiagnosticUpdateSourceRegistrationService);
        var compilerEngineCompilation = (CSharpCompilation)compilerEngineWorkspace.CurrentSolution.Projects.Single().GetRequiredCompilationAsync(CancellationToken.None).Result;
        compilerEngineCompilation.GetAnalyzerDiagnostics([compilerEngineAnalyzer]);
        foreach (var method in methodNames)
        {
            Assert.False(compilerEngineAnalyzer.CallLog.Any(e => e.CallerName == method && e.MethodKind == MethodKind.DelegateInvoke && e.ReturnsVoid));
            Assert.False(compilerEngineAnalyzer.CallLog.Any(e => e.CallerName == method && e.MethodKind == MethodKind.DelegateInvoke && !e.ReturnsVoid));
            Assert.True(compilerEngineAnalyzer.CallLog.Any(e => e.CallerName == method && e.SymbolKind == SymbolKind.NamedType));
            Assert.False(compilerEngineAnalyzer.CallLog.Any(e => e.CallerName == method && e.SymbolKind == SymbolKind.Property));
        }
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/759")]
    public async Task DiagnosticAnalyzerDriverIsSafeAgainstAnalyzerExceptions()
    {
        var source = TestResource.AllInOneCSharpCode;
 
        await ThrowingDiagnosticAnalyzer<SyntaxKind>.VerifyAnalyzerEngineIsSafeAgainstExceptionsAsync(async analyzer =>
        {
            using var workspace = EditorTestWorkspace.CreateCSharp(source, TestOptions.Regular, composition: s_compositionWithMockDiagnosticUpdateSourceRegistrationService);
 
            var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create(analyzer));
            workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences([analyzerReference]));
 
            var document = workspace.CurrentSolution.Projects.Single().Documents.Single();
            return await DiagnosticProviderTestUtilities.GetAllDiagnosticsAsync(workspace, document, new TextSpan(0, document.GetTextAsync().Result.Length));
        });
    }
 
    [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/908621")]
    public void DiagnosticServiceIsSafeAgainstAnalyzerExceptions_1()
    {
        var analyzer = new ThrowingDiagnosticAnalyzer<SyntaxKind>();
        analyzer.ThrowOn(typeof(DiagnosticAnalyzer).GetProperties().Single().Name);
        AccessSupportedDiagnostics(analyzer);
    }
 
    [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/908621")]
    public void DiagnosticServiceIsSafeAgainstAnalyzerExceptions_2()
    {
        var analyzer = new ThrowingDoNotCatchDiagnosticAnalyzer<SyntaxKind>();
        analyzer.ThrowOn(typeof(DiagnosticAnalyzer).GetProperties().Single().Name);
        var exceptions = new List<Exception>();
        try
        {
            AccessSupportedDiagnostics(analyzer);
        }
        catch (Exception e)
        {
            exceptions.Add(e);
        }
 
        Assert.Empty(exceptions);
    }
 
    [Fact]
    public async Task AnalyzerOptionsArePassedToAllAnalyzers()
    {
        using var workspace = EditorTestWorkspace.CreateCSharp(TestResource.AllInOneCSharpCode, TestOptions.Regular, composition: s_compositionWithMockDiagnosticUpdateSourceRegistrationService);
 
        var additionalDocId = DocumentId.CreateNewId(workspace.CurrentSolution.Projects.Single().Id);
        var additionalText = new TestAdditionalText("add.config", SourceText.From("random text"));
        var options = new AnalyzerOptions(ImmutableArray.Create<AdditionalText>(additionalText));
        var analyzer = new OptionsDiagnosticAnalyzer<SyntaxKind>(expectedOptions: options);
        var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create<DiagnosticAnalyzer>(analyzer));
 
        workspace.TryApplyChanges(workspace.CurrentSolution
            .WithAnalyzerReferences([analyzerReference])
            .AddAdditionalDocument(additionalDocId, "add.config", additionalText.GetText()!));
 
        var sourceDocument = workspace.CurrentSolution.Projects.Single().Documents.Single();
        await DiagnosticProviderTestUtilities.GetAllDiagnosticsAsync(workspace, sourceDocument, new TextSpan(0, sourceDocument.GetTextAsync().Result.Length));
        analyzer.VerifyAnalyzerOptions();
    }
 
    private static void AccessSupportedDiagnostics(DiagnosticAnalyzer analyzer)
    {
        var diagnosticService = new HostDiagnosticAnalyzers([new AnalyzerImageReference(ImmutableArray.Create(analyzer))]);
        diagnosticService.GetDiagnosticDescriptorsPerReference(new DiagnosticAnalyzerInfoCache());
    }
 
    private class ThrowingDoNotCatchDiagnosticAnalyzer<TLanguageKindEnum> : ThrowingDiagnosticAnalyzer<TLanguageKindEnum>, IBuiltInAnalyzer where TLanguageKindEnum : struct
    {
        public bool IsHighPriority => false;
 
        public DiagnosticAnalyzerCategory GetAnalyzerCategory()
            => DiagnosticAnalyzerCategory.SyntaxTreeWithoutSemanticsAnalysis | DiagnosticAnalyzerCategory.SemanticDocumentAnalysis;
    }
 
    [Fact]
    public async Task AnalyzerCreatedAtCompilationLevelNeedNotBeCompilationAnalyzer()
    {
        var source = @"x";
 
        using var workspace = EditorTestWorkspace.CreateCSharp(source, composition: s_compositionWithMockDiagnosticUpdateSourceRegistrationService);
 
        var analyzer = new CompilationAnalyzerWithSyntaxTreeAnalyzer();
        var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create<DiagnosticAnalyzer>(analyzer));
        workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences([analyzerReference]));
 
        var ideEngineDocument = workspace.CurrentSolution.Projects.Single().Documents.Single();
        var diagnostics = await DiagnosticProviderTestUtilities.GetAllDiagnosticsAsync(workspace, ideEngineDocument, new TextSpan(0, ideEngineDocument.GetTextAsync().Result.Length));
 
        var diagnosticsFromAnalyzer = diagnostics.Where(d => d.Id == "SyntaxDiagnostic");
 
        Assert.Equal(1, diagnosticsFromAnalyzer.Count());
    }
 
    private class CompilationAnalyzerWithSyntaxTreeAnalyzer : DiagnosticAnalyzer
    {
        private const string ID = "SyntaxDiagnostic";
 
        private static readonly DiagnosticDescriptor s_syntaxDiagnosticDescriptor =
            new DiagnosticDescriptor(ID, title: "Syntax", messageFormat: "Syntax", category: "Test", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true);
 
        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
        {
            get
            {
                return ImmutableArray.Create(s_syntaxDiagnosticDescriptor);
            }
        }
 
        public override void Initialize(AnalysisContext context)
            => context.RegisterCompilationStartAction(CreateAnalyzerWithinCompilation);
 
        public void CreateAnalyzerWithinCompilation(CompilationStartAnalysisContext context)
            => context.RegisterSyntaxTreeAction(SyntaxTreeAnalyzer.AnalyzeSyntaxTree);
 
        private class SyntaxTreeAnalyzer
        {
            public static void AnalyzeSyntaxTree(SyntaxTreeAnalysisContext context)
                => context.ReportDiagnostic(Diagnostic.Create(s_syntaxDiagnosticDescriptor, context.Tree.GetRoot().GetFirstToken().GetLocation()));
        }
    }
 
    [Fact]
    public async Task CodeBlockAnalyzersOnlyAnalyzeExecutableCode()
    {
        var source = """
            using System;
            class C
            {
                void F(int x = 0)
                {
                    Console.WriteLine(0);
                }
            }
            """;
 
        var analyzer = new CodeBlockAnalyzerFactory();
        using (var ideEngineWorkspace = EditorTestWorkspace.CreateCSharp(source, composition: s_compositionWithMockDiagnosticUpdateSourceRegistrationService))
        {
            var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create<DiagnosticAnalyzer>(analyzer));
            ideEngineWorkspace.TryApplyChanges(ideEngineWorkspace.CurrentSolution.WithAnalyzerReferences([analyzerReference]));
 
            var ideEngineDocument = ideEngineWorkspace.CurrentSolution.Projects.Single().Documents.Single();
            var diagnostics = await DiagnosticProviderTestUtilities.GetAllDiagnosticsAsync(ideEngineWorkspace, ideEngineDocument, new TextSpan(0, ideEngineDocument.GetTextAsync().Result.Length));
            var diagnosticsFromAnalyzer = diagnostics.Where(d => d.Id == CodeBlockAnalyzerFactory.Descriptor.Id);
            Assert.Equal(2, diagnosticsFromAnalyzer.Count());
        }
 
        source = """
            using System;
            class C
            {
                void F(int x = 0, int y = 1, int z = 2)
                {
                    Console.WriteLine(0);
                }
            }
            """;
 
        using (var compilerEngineWorkspace = EditorTestWorkspace.CreateCSharp(source, composition: s_compositionWithMockDiagnosticUpdateSourceRegistrationService))
        {
            var compilerEngineCompilation = (CSharpCompilation)compilerEngineWorkspace.CurrentSolution.Projects.Single().GetRequiredCompilationAsync(CancellationToken.None).Result;
            var diagnostics = compilerEngineCompilation.GetAnalyzerDiagnostics([analyzer]);
            var diagnosticsFromAnalyzer = diagnostics.Where(d => d.Id == CodeBlockAnalyzerFactory.Descriptor.Id);
            Assert.Equal(4, diagnosticsFromAnalyzer.Count());
        }
    }
 
    private class CodeBlockAnalyzerFactory : DiagnosticAnalyzer
    {
        public static DiagnosticDescriptor Descriptor = DescriptorFactory.CreateSimpleDescriptor("DummyDiagnostic");
 
        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
        {
            get
            {
                return ImmutableArray.Create(Descriptor);
            }
        }
 
        public override void Initialize(AnalysisContext context)
            => context.RegisterCodeBlockStartAction<SyntaxKind>(CreateAnalyzerWithinCodeBlock);
 
        public void CreateAnalyzerWithinCodeBlock(CodeBlockStartAnalysisContext<SyntaxKind> context)
        {
            var blockAnalyzer = new CodeBlockAnalyzer();
            context.RegisterCodeBlockEndAction(CodeBlockAnalyzer.AnalyzeCodeBlock);
            context.RegisterSyntaxNodeAction(CodeBlockAnalyzer.AnalyzeNode, CodeBlockAnalyzer.SyntaxKindsOfInterest.ToArray());
        }
 
        private class CodeBlockAnalyzer
        {
            public static ImmutableArray<SyntaxKind> SyntaxKindsOfInterest
            {
                get
                {
                    return ImmutableArray.Create(SyntaxKind.MethodDeclaration, SyntaxKind.ExpressionStatement, SyntaxKind.EqualsValueClause);
                }
            }
 
            public static void AnalyzeCodeBlock(CodeBlockAnalysisContext _)
            {
            }
 
            public static void AnalyzeNode(SyntaxNodeAnalysisContext context)
            {
                // Ensure only executable nodes are analyzed.
                Assert.NotEqual(SyntaxKind.MethodDeclaration, context.Node.Kind());
                context.ReportDiagnostic(Diagnostic.Create(Descriptor, context.Node.GetLocation()));
            }
        }
    }
 
    [Fact]
    public async Task TestDiagnosticSpan()
    {
        var source = @"// empty code";
 
        var analyzer = new InvalidSpanAnalyzer();
        using var compilerEngineWorkspace = EditorTestWorkspace.CreateCSharp(source);
        var compilerEngineCompilation = (CSharpCompilation)(await compilerEngineWorkspace.CurrentSolution.Projects.Single().GetRequiredCompilationAsync(CancellationToken.None));
 
        var diagnostics = compilerEngineCompilation.GetAnalyzerDiagnostics([analyzer]);
        AssertEx.Any(diagnostics, d => d.Id == DocumentAnalysisExecutor.AnalyzerExceptionDiagnosticId);
    }
 
    private class InvalidSpanAnalyzer : DiagnosticAnalyzer
    {
        public static DiagnosticDescriptor Descriptor = DescriptorFactory.CreateSimpleDescriptor("DummyDiagnostic");
 
        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
            => ImmutableArray.Create(Descriptor);
 
        public override void Initialize(AnalysisContext context)
            => context.RegisterSyntaxTreeAction(Analyze);
 
        private void Analyze(SyntaxTreeAnalysisContext context)
            => context.ReportDiagnostic(Diagnostic.Create(Descriptor, Location.Create(context.Tree, TextSpan.FromBounds(1000, 2000))));
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/18818")]
    public async Task TestNuGetAndVsixAnalyzer_ReportsSameId()
    {
        // NuGet and VSIX analyzer reporting same diagnostic IDs.
        var reportedDiagnosticIds = new[] { "A", "B", "C" };
        var nugetAnalyzer = new NuGetAnalyzer(reportedDiagnosticIds);
        var vsixAnalyzer = new VsixAnalyzer(reportedDiagnosticIds);
        Assert.Equal(reportedDiagnosticIds, nugetAnalyzer.SupportedDiagnostics.Select(d => d.Id).Order());
        Assert.Equal(reportedDiagnosticIds, vsixAnalyzer.SupportedDiagnostics.Select(d => d.Id).Order());
 
        // No NuGet or VSIX analyzer - no diagnostics.
        await TestNuGetAndVsixAnalyzerCoreAsync(
            nugetAnalyzer: null,
            expectedNugetAnalyzerExecuted: false,
            vsixAnalyzer: null,
            expectedVsixAnalyzerExecuted: false);
 
        // Only NuGet analyzer - verify diagnostics.
        await TestNuGetAndVsixAnalyzerCoreAsync(
            nugetAnalyzer,
            expectedNugetAnalyzerExecuted: true,
            vsixAnalyzer: null,
            expectedVsixAnalyzerExecuted: false,
            new[]
            {
                (Diagnostic("A", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
                (Diagnostic("B", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
                (Diagnostic("C", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
            });
 
        // Only VSIX analyzer - verify diagnostics.
        await TestNuGetAndVsixAnalyzerCoreAsync(
            nugetAnalyzer: null,
            expectedNugetAnalyzerExecuted: false,
            vsixAnalyzer,
            expectedVsixAnalyzerExecuted: true,
            new[]
            {
                (Diagnostic("A", "Class").WithLocation(1, 7), nameof(VsixAnalyzer)),
                (Diagnostic("B", "Class").WithLocation(1, 7), nameof(VsixAnalyzer)),
                (Diagnostic("C", "Class").WithLocation(1, 7), nameof(VsixAnalyzer)),
            });
 
        // Both NuGet and VSIX analyzer, verify the following:
        //   1) No duplicate diagnostics
        //   2) Only NuGet analyzer executes
        await TestNuGetAndVsixAnalyzerCoreAsync(
            nugetAnalyzer,
            expectedNugetAnalyzerExecuted: true,
            vsixAnalyzer,
            expectedVsixAnalyzerExecuted: false,
            new[]
            {
                (Diagnostic("A", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
                (Diagnostic("B", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
                (Diagnostic("C", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
            });
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/18818")]
    public async Task TestNuGetAndVsixAnalyzer_NuGetAnalyzerReportsSubsetOfVsixAnalyzerIds()
    {
        // NuGet analyzer reports subset of diagnostic IDs reported by VSIX analyzer.
        var nugetAnalyzerDiagnosticIds = new[] { "B" };
        var vsixAnalyzerDiagnosticIds = new[] { "A", "B", "C" };
        var nugetAnalyzer = new NuGetAnalyzer(nugetAnalyzerDiagnosticIds);
        var vsixAnalyzer = new VsixAnalyzer(vsixAnalyzerDiagnosticIds);
        Assert.Equal(nugetAnalyzerDiagnosticIds, nugetAnalyzer.SupportedDiagnostics.Select(d => d.Id).Order());
        Assert.Equal(vsixAnalyzerDiagnosticIds, vsixAnalyzer.SupportedDiagnostics.Select(d => d.Id).Order());
 
        // Only NuGet analyzer - verify diagnostics.
        await TestNuGetAndVsixAnalyzerCoreAsync(
            nugetAnalyzer,
            expectedNugetAnalyzerExecuted: true,
            vsixAnalyzer: null,
            expectedVsixAnalyzerExecuted: false,
            new[]
            {
                (Diagnostic("B", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer))
            });
 
        // Only VSIX analyzer - verify diagnostics.
        await TestNuGetAndVsixAnalyzerCoreAsync(
            nugetAnalyzer: null,
            expectedNugetAnalyzerExecuted: false,
            vsixAnalyzer,
            expectedVsixAnalyzerExecuted: true,
            new[]
            {
                (Diagnostic("A", "Class").WithLocation(1, 7), nameof(VsixAnalyzer)),
                (Diagnostic("B", "Class").WithLocation(1, 7), nameof(VsixAnalyzer)),
                (Diagnostic("C", "Class").WithLocation(1, 7), nameof(VsixAnalyzer)),
            });
 
        // Both NuGet and VSIX analyzer, verify the following:
        //   1) No duplicate diagnostics
        //   2) Both NuGet and Vsix analyzers execute
        //   3) Appropriate diagnostic filtering is done - NuGet analyzer reported diagnostic IDs are filtered from Vsix analyzer execution.
        await TestNuGetAndVsixAnalyzerCoreAsync(
            nugetAnalyzer,
            expectedNugetAnalyzerExecuted: true,
            vsixAnalyzer,
            expectedVsixAnalyzerExecuted: true,
            new[]
            {
                (Diagnostic("A", "Class").WithLocation(1, 7), nameof(VsixAnalyzer)),
                (Diagnostic("B", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
                (Diagnostic("C", "Class").WithLocation(1, 7), nameof(VsixAnalyzer)),
            });
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/18818")]
    public async Task TestNuGetAndVsixAnalyzer_VsixAnalyzerReportsSubsetOfNuGetAnalyzerIds()
    {
        // VSIX analyzer reports subset of diagnostic IDs reported by NuGet analyzer.
        var nugetAnalyzerDiagnosticIds = new[] { "A", "B", "C" };
        var vsixAnalyzerDiagnosticIds = new[] { "B" };
        var nugetAnalyzer = new NuGetAnalyzer(nugetAnalyzerDiagnosticIds);
        var vsixAnalyzer = new VsixAnalyzer(vsixAnalyzerDiagnosticIds);
        Assert.Equal(nugetAnalyzerDiagnosticIds, nugetAnalyzer.SupportedDiagnostics.Select(d => d.Id).Order());
        Assert.Equal(vsixAnalyzerDiagnosticIds, vsixAnalyzer.SupportedDiagnostics.Select(d => d.Id).Order());
 
        // Only NuGet analyzer - verify diagnostics.
        await TestNuGetAndVsixAnalyzerCoreAsync(
            nugetAnalyzer,
            expectedNugetAnalyzerExecuted: true,
            vsixAnalyzer: null,
            expectedVsixAnalyzerExecuted: false,
            new[]
            {
                (Diagnostic("A", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
                (Diagnostic("B", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
                (Diagnostic("C", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer))
            });
 
        // Only VSIX analyzer - verify diagnostics.
        await TestNuGetAndVsixAnalyzerCoreAsync(
            nugetAnalyzer: null,
            expectedNugetAnalyzerExecuted: false,
            vsixAnalyzer,
            expectedVsixAnalyzerExecuted: true,
            new[]
            {
                (Diagnostic("B", "Class").WithLocation(1, 7), nameof(VsixAnalyzer))
            });
 
        // Both NuGet and VSIX analyzer, verify the following:
        //   1) No duplicate diagnostics
        //   2) Only NuGet analyzer executes
        await TestNuGetAndVsixAnalyzerCoreAsync(
            nugetAnalyzer,
            expectedNugetAnalyzerExecuted: true,
            vsixAnalyzer,
            expectedVsixAnalyzerExecuted: false,
            new[]
            {
                (Diagnostic("A", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
                (Diagnostic("B", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
                (Diagnostic("C", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
            });
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/18818")]
    public async Task TestNuGetAndVsixAnalyzer_MultipleNuGetAnalyzersCollectivelyReportSameIds()
    {
        // Multiple NuGet analyzers collectively report same diagnostic IDs reported by Vsix analyzer.
        var firstNugetAnalyzerDiagnosticIds = new[] { "B" };
        var secondNugetAnalyzerDiagnosticIds = new[] { "A", "C" };
        var vsixAnalyzerDiagnosticIds = new[] { "A", "B", "C" };
        var firstNugetAnalyzer = new NuGetAnalyzer(firstNugetAnalyzerDiagnosticIds);
        var secondNugetAnalyzer = new NuGetAnalyzer(secondNugetAnalyzerDiagnosticIds);
        var vsixAnalyzer = new VsixAnalyzer(vsixAnalyzerDiagnosticIds);
        Assert.Equal(firstNugetAnalyzerDiagnosticIds, firstNugetAnalyzer.SupportedDiagnostics.Select(d => d.Id).Order());
        Assert.Equal(secondNugetAnalyzerDiagnosticIds, secondNugetAnalyzer.SupportedDiagnostics.Select(d => d.Id).Order());
        Assert.Equal(vsixAnalyzerDiagnosticIds, vsixAnalyzer.SupportedDiagnostics.Select(d => d.Id).Order());
 
        // All NuGet analyzers and no Vsix analyzer, verify the following:
        //   1) No duplicate diagnostics
        //   2) Only NuGet analyzers execute
        await TestNuGetAndVsixAnalyzerCoreAsync(
            nugetAnalyzers: ImmutableArray.Create(firstNugetAnalyzer, secondNugetAnalyzer),
            expectedNugetAnalyzersExecuted: true,
            vsixAnalyzers: ImmutableArray<VsixAnalyzer>.Empty,
            expectedVsixAnalyzersExecuted: false,
            nugetSuppressors: ImmutableArray<NuGetSuppressor>.Empty,
            expectedNugetSuppressorsExecuted: false,
            vsixSuppressors: ImmutableArray<VsixSuppressor>.Empty,
            expectedVsixSuppressorsExecuted: false,
            new[]
            {
                (Diagnostic("A", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
                (Diagnostic("B", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
                (Diagnostic("C", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer))
            });
 
        // All NuGet analyzers and Vsix analyzer, verify the following:
        //   1) No duplicate diagnostics
        //   2) Only NuGet analyzers execute
        await TestNuGetAndVsixAnalyzerCoreAsync(
            nugetAnalyzers: ImmutableArray.Create(firstNugetAnalyzer, secondNugetAnalyzer),
            expectedNugetAnalyzersExecuted: true,
            vsixAnalyzers: ImmutableArray.Create(vsixAnalyzer),
            expectedVsixAnalyzersExecuted: false,
            nugetSuppressors: ImmutableArray<NuGetSuppressor>.Empty,
            expectedNugetSuppressorsExecuted: false,
            vsixSuppressors: ImmutableArray<VsixSuppressor>.Empty,
            expectedVsixSuppressorsExecuted: false,
            new[]
            {
                (Diagnostic("A", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
                (Diagnostic("B", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
                (Diagnostic("C", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer))
            });
 
        // Subset of NuGet analyzers and Vsix analyzer, verify the following:
        //   1) No duplicate diagnostics
        //   2) Both NuGet and Vsix analyzers execute
        //   3) Appropriate diagnostic filtering is done - NuGet analyzer reported diagnostic IDs are filtered from Vsix analyzer execution.
        await TestNuGetAndVsixAnalyzerCoreAsync(
            nugetAnalyzers: ImmutableArray.Create(firstNugetAnalyzer),
            expectedNugetAnalyzersExecuted: true,
            vsixAnalyzers: ImmutableArray.Create(vsixAnalyzer),
            expectedVsixAnalyzersExecuted: true,
            nugetSuppressors: ImmutableArray<NuGetSuppressor>.Empty,
            expectedNugetSuppressorsExecuted: false,
            vsixSuppressors: ImmutableArray<VsixSuppressor>.Empty,
            expectedVsixSuppressorsExecuted: false,
            new[]
            {
                (Diagnostic("A", "Class").WithLocation(1, 7), nameof(VsixAnalyzer)),
                (Diagnostic("B", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
                (Diagnostic("C", "Class").WithLocation(1, 7), nameof(VsixAnalyzer))
            });
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/46942")]
    public async Task TestNuGetAndVsixAnalyzer_SuppressorSuppressesVsixAnalyzer()
    {
        // Multiple NuGet analyzers do not overlap with the VSIX analyzer or suppressor
        var firstNugetAnalyzerDiagnosticIds = new[] { "A" };
        var secondNugetAnalyzerDiagnosticIds = new[] { "B", "C" };
        var vsixAnalyzerDiagnosticIds = new[] { "X", "Y", "Z" };
        var firstNugetAnalyzer = new NuGetAnalyzer(firstNugetAnalyzerDiagnosticIds);
        var secondNugetAnalyzer = new NuGetAnalyzer(secondNugetAnalyzerDiagnosticIds);
        var vsixAnalyzer = new VsixAnalyzer(vsixAnalyzerDiagnosticIds);
        var vsixSuppressor = new VsixSuppressor(vsixAnalyzerDiagnosticIds);
        var nugetSuppressor = new NuGetSuppressor(vsixAnalyzerDiagnosticIds);
        var partialNugetSuppressor = new NuGetSuppressor(["Y", "Z"]);
 
        Assert.Equal(firstNugetAnalyzerDiagnosticIds, firstNugetAnalyzer.SupportedDiagnostics.Select(d => d.Id).Order());
        Assert.Equal(secondNugetAnalyzerDiagnosticIds, secondNugetAnalyzer.SupportedDiagnostics.Select(d => d.Id).Order());
        Assert.Equal(vsixAnalyzerDiagnosticIds, vsixAnalyzer.SupportedDiagnostics.Select(d => d.Id).Order());
        Assert.Equal(vsixAnalyzerDiagnosticIds, vsixSuppressor.SupportedSuppressions.Select(s => s.SuppressedDiagnosticId).Order());
        Assert.Equal(vsixAnalyzerDiagnosticIds, nugetSuppressor.SupportedSuppressions.Select(s => s.SuppressedDiagnosticId).Order());
 
        // Verify the following:
        //   1) No duplicate diagnostics
        //   2) The VSIX diagnostics are suppressed by the VSIX suppressor
        await TestNuGetAndVsixAnalyzerCoreAsync(
            nugetAnalyzers: ImmutableArray<NuGetAnalyzer>.Empty,
            expectedNugetAnalyzersExecuted: false,
            vsixAnalyzers: ImmutableArray.Create(vsixAnalyzer),
            expectedVsixAnalyzersExecuted: true,
            nugetSuppressors: ImmutableArray<NuGetSuppressor>.Empty,
            expectedNugetSuppressorsExecuted: false,
            vsixSuppressors: ImmutableArray.Create(vsixSuppressor),
            expectedVsixSuppressorsExecuted: true,
            new[]
            {
                (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))
            });
 
        // All without overlap, the VSIX analyzer and suppressor still work when nuget analyzers are present:
        //   1) No duplicate diagnostics
        //   2) All analyzers execute
        //   3) VSIX diagnostics are suppressed.
        await TestNuGetAndVsixAnalyzerCoreAsync(
            nugetAnalyzers: ImmutableArray.Create(firstNugetAnalyzer, secondNugetAnalyzer),
            expectedNugetAnalyzersExecuted: true,
            vsixAnalyzers: ImmutableArray.Create(vsixAnalyzer),
            expectedVsixAnalyzersExecuted: true,
            nugetSuppressors: ImmutableArray<NuGetSuppressor>.Empty,
            expectedNugetSuppressorsExecuted: false,
            vsixSuppressors: ImmutableArray.Create(vsixSuppressor),
            expectedVsixSuppressorsExecuted: true,
            new[]
            {
                (Diagnostic("A", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
                (Diagnostic("B", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
                (Diagnostic("C", "Class").WithLocation(1, 7), nameof(NuGetAnalyzer)),
                (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))
            });
 
        // All without overlap, verify the following:
        //   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,
            vsixSuppressors: ImmutableArray<VsixSuppressor>.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))
            });
 
        // Suppressors with duplicate support for VsixAnalyzer, but not 100% overlap. Verify the following:
        //   1) No duplicate diagnostics
        //   2) Both NuGet and Vsix analyzers execute
        //   3) Only Nuget suppressor executes
        //   4) Appropriate diagnostic filtering is done - Nuget suppressor suppresses VSIX analyzer.
        await TestNuGetAndVsixAnalyzerCoreAsync(
            nugetAnalyzers: ImmutableArray.Create(firstNugetAnalyzer),
            expectedNugetAnalyzersExecuted: true,
            vsixAnalyzers: ImmutableArray.Create(vsixAnalyzer),
            expectedVsixAnalyzersExecuted: true,
            nugetSuppressors: ImmutableArray.Create(partialNugetSuppressor),
            expectedNugetSuppressorsExecuted: FalseButShouldBeTrue,
            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))
            });
 
        // Suppressors with duplicate support for VsixAnalyzer, with 100% overlap. Verify the following:
        //   1) No duplicate diagnostics
        //   2) Both NuGet and Vsix analyzers execute
        //   3) Only Nuget suppressor executes
        //   4) Appropriate diagnostic filtering is done - Nuget suppressor suppresses VSIX analyzer.
        await TestNuGetAndVsixAnalyzerCoreAsync(
            nugetAnalyzers: ImmutableArray.Create(firstNugetAnalyzer),
            expectedNugetAnalyzersExecuted: true,
            vsixAnalyzers: ImmutableArray.Create(vsixAnalyzer),
            expectedVsixAnalyzersExecuted: true,
            nugetSuppressors: ImmutableArray.Create(nugetSuppressor),
            expectedNugetSuppressorsExecuted: FalseButShouldBeTrue,
            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))
            });
    }
 
    private static Task TestNuGetAndVsixAnalyzerCoreAsync(
        NuGetAnalyzer? nugetAnalyzer,
        bool expectedNugetAnalyzerExecuted,
        VsixAnalyzer? vsixAnalyzer,
        bool expectedVsixAnalyzerExecuted,
        params (DiagnosticDescription diagnostic, string message)[] expectedDiagnostics)
        => TestNuGetAndVsixAnalyzerCoreAsync(
            nugetAnalyzer != null ? ImmutableArray.Create(nugetAnalyzer) : ImmutableArray<NuGetAnalyzer>.Empty,
            expectedNugetAnalyzerExecuted,
            vsixAnalyzer != null ? ImmutableArray.Create(vsixAnalyzer) : ImmutableArray<VsixAnalyzer>.Empty,
            expectedVsixAnalyzerExecuted,
            ImmutableArray<NuGetSuppressor>.Empty,
            false,
            ImmutableArray<VsixSuppressor>.Empty,
            false,
            expectedDiagnostics);
 
    private static async Task TestNuGetAndVsixAnalyzerCoreAsync(
        ImmutableArray<NuGetAnalyzer> nugetAnalyzers,
        bool expectedNugetAnalyzersExecuted,
        ImmutableArray<VsixAnalyzer> vsixAnalyzers,
        bool expectedVsixAnalyzersExecuted,
        ImmutableArray<NuGetSuppressor> nugetSuppressors,
        bool expectedNugetSuppressorsExecuted,
        ImmutableArray<VsixSuppressor> vsixSuppressors,
        bool expectedVsixSuppressorsExecuted,
        params (DiagnosticDescription diagnostic, string message)[] expectedDiagnostics)
    {
        // First clear out the analyzer state for all analyzers.
        foreach (var nugetAnalyzer in nugetAnalyzers)
        {
            nugetAnalyzer.SymbolActionInvoked = false;
        }
 
        foreach (var vsixAnalyzer in vsixAnalyzers)
        {
            vsixAnalyzer.SymbolActionInvoked = false;
        }
 
        foreach (var nugetSuppressor in nugetSuppressors)
        {
            nugetSuppressor.SuppressorInvoked = false;
        }
 
        foreach (var vsixSuppressor in vsixSuppressors)
        {
            vsixSuppressor.SuppressorInvoked = false;
        }
 
        using var workspace = EditorTestWorkspace.CreateCSharp("class Class { }", TestOptions.Regular, composition: s_compositionWithMockDiagnosticUpdateSourceRegistrationService);
        var vsixAnalyzerReferences = new List<DiagnosticAnalyzer>(vsixAnalyzers.CastArray<DiagnosticAnalyzer>());
        vsixAnalyzerReferences.AddRange(vsixSuppressors.CastArray<DiagnosticAnalyzer>());
 
        Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(
        [
            new AnalyzerImageReference(vsixAnalyzerReferences.ToImmutableArray())
        ])));
 
        var project = workspace.CurrentSolution.Projects.Single();
 
        var nugetAnalyzerReferences = new List<DiagnosticAnalyzer>();
        if (!nugetAnalyzers.IsEmpty)
        {
            nugetAnalyzerReferences.AddRange(nugetAnalyzers.As<DiagnosticAnalyzer>());
        }
 
        if (!nugetSuppressors.IsEmpty)
        {
            nugetAnalyzerReferences.AddRange(nugetSuppressors.As<DiagnosticAnalyzer>());
        }
 
        if (nugetAnalyzerReferences.Count > 0)
        {
            project = project.WithAnalyzerReferences([new AnalyzerImageReference(nugetAnalyzerReferences.ToImmutableArray())]);
        }
 
        var document = project.Documents.Single();
        var root = await document.GetRequiredSyntaxRootAsync(CancellationToken.None);
 
        var diagnostics = (await DiagnosticProviderTestUtilities.GetAllDiagnosticsAsync(workspace, document, root.FullSpan, includeSuppressedDiagnostics: true))
            .OrderBy(d => d.Id).ToImmutableArray();
 
        diagnostics.Verify(expectedDiagnostics.Select(d => d.diagnostic).ToArray());
 
        var index = 0;
        foreach (var (d, expectedMessage) in expectedDiagnostics)
        {
            Assert.Equal(expectedMessage, diagnostics[index].GetMessage());
            Assert.Equal(d.IsSuppressed, diagnostics[index].IsSuppressed);
            index++;
        }
 
        foreach (var nugetAnalyzer in nugetAnalyzers)
        {
            Assert.Equal(expectedNugetAnalyzersExecuted, nugetAnalyzer.SymbolActionInvoked);
        }
 
        foreach (var vsixAnalyzer in vsixAnalyzers)
        {
            Assert.Equal(expectedVsixAnalyzersExecuted, vsixAnalyzer.SymbolActionInvoked);
        }
 
        foreach (var nugetSuppressor in nugetSuppressors)
        {
            Assert.Equal(expectedNugetSuppressorsExecuted, nugetSuppressor.SuppressorInvoked);
        }
 
        foreach (var vsixSuppressor in vsixSuppressors)
        {
            Assert.Equal(expectedVsixSuppressorsExecuted, vsixSuppressor.SuppressorInvoked);
        }
    }
 
    [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
    private sealed class NuGetAnalyzer : AbstractNuGetOrVsixAnalyzer
    {
        public NuGetAnalyzer(string[] reportedIds)
            : base(nameof(NuGetAnalyzer), reportedIds)
        {
        }
    }
 
    [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
    private sealed class VsixAnalyzer : AbstractNuGetOrVsixAnalyzer
    {
        public VsixAnalyzer(string[] reportedIds)
            : base(nameof(VsixAnalyzer), reportedIds)
        {
        }
    }
 
    private abstract class AbstractNuGetOrVsixAnalyzer : DiagnosticAnalyzer
    {
        protected AbstractNuGetOrVsixAnalyzer(string analyzerName, params string[] reportedIds)
            => SupportedDiagnostics = CreateSupportedDiagnostics(analyzerName, reportedIds);
 
        private static ImmutableArray<DiagnosticDescriptor> CreateSupportedDiagnostics(string analyzerName, string[] reportedIds)
        {
            var builder = ArrayBuilder<DiagnosticDescriptor>.GetInstance(reportedIds.Length);
            foreach (var id in reportedIds)
            {
                var descriptor = new DiagnosticDescriptor(id, "Title", messageFormat: analyzerName, "Category", DiagnosticSeverity.Warning, isEnabledByDefault: true);
                builder.Add(descriptor);
            }
 
            return builder.ToImmutableAndFree();
        }
 
        public bool SymbolActionInvoked { get; set; }
        public sealed override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
        public sealed override void Initialize(AnalysisContext context)
            => context.RegisterSymbolAction(OnSymbol, SymbolKind.NamedType);
 
        private void OnSymbol(SymbolAnalysisContext context)
        {
            SymbolActionInvoked = true;
            foreach (var descriptor in SupportedDiagnostics)
            {
                var diagnostic = Diagnostic.Create(descriptor, context.Symbol.Locations[0]);
                context.ReportDiagnostic(diagnostic);
            }
        }
    }
 
    private sealed class NuGetSuppressor : AbstractNugetOrVsixSuppressor
    {
        public NuGetSuppressor(string[] reportIds)
            : base(nameof(NuGetSuppressor), reportIds)
        {
        }
    }
 
    private sealed class VsixSuppressor : AbstractNugetOrVsixSuppressor
    {
        public VsixSuppressor(string[] reportIds)
            : base(nameof(VsixSuppressor), reportIds)
        {
        }
    }
 
    private abstract class AbstractNugetOrVsixSuppressor : DiagnosticSuppressor
    {
        private readonly Dictionary<string, SuppressionDescriptor> mapping = [];
 
        protected AbstractNugetOrVsixSuppressor(string analyzerName, params string[] reportedIds)
            => SupportedSuppressions = CreateSupportedSuppressions(analyzerName, this.mapping, reportedIds);
 
        private static ImmutableArray<SuppressionDescriptor> CreateSupportedSuppressions(
            string analyzerName,
            Dictionary<string, SuppressionDescriptor> mapping,
            string[] reportedIds)
        {
            var builder = ArrayBuilder<SuppressionDescriptor>.GetInstance(reportedIds.Length);
            foreach (var id in reportedIds)
            {
                var descriptor = new SuppressionDescriptor("SPR" + id, id, justification: analyzerName);
                mapping.Add(descriptor.SuppressedDiagnosticId, descriptor);
                builder.Add(descriptor);
            }
 
            return builder.ToImmutableAndFree();
        }
 
        public bool SuppressorInvoked { get; set; }
 
        public sealed override ImmutableArray<SuppressionDescriptor> SupportedSuppressions { get; }
 
        public override void ReportSuppressions(SuppressionAnalysisContext context)
        {
            SuppressorInvoked = true;
 
            foreach (var diagnostic in context.ReportedDiagnostics)
            {
                if (this.mapping.TryGetValue(diagnostic.Id, out var descriptor))
                {
                    context.ReportSuppression(
                        Microsoft.CodeAnalysis.Diagnostics.Suppression.Create(descriptor, diagnostic));
                }
            }
        }
    }
}