From 73afff6495059eda70a83d4545bd16ed2ede2a2c Mon Sep 17 00:00:00 2001
From: Gabriela Trutan <gabriela.trutan@sonarsource.com>
Date: Thu, 14 Nov 2024 12:08:52 +0100
Subject: [PATCH 1/6] SLVS-1625 CaYC Education and its test class

---
 src/Education.UnitTests/EducationTests.cs | 277 ++++++++++------------
 src/Education/Education.cs                | 145 ++++++-----
 2 files changed, 199 insertions(+), 223 deletions(-)

diff --git a/src/Education.UnitTests/EducationTests.cs b/src/Education.UnitTests/EducationTests.cs
index c049485d9e..dec87b3078 100644
--- a/src/Education.UnitTests/EducationTests.cs
+++ b/src/Education.UnitTests/EducationTests.cs
@@ -19,163 +19,140 @@
  */
 
 using System.Windows.Documents;
-using Moq;
 using SonarLint.VisualStudio.Core;
-using SonarLint.VisualStudio.Core.Suppressions;
 using SonarLint.VisualStudio.Education.Rule;
 using SonarLint.VisualStudio.Education.XamlGenerator;
 using SonarLint.VisualStudio.TestInfrastructure;
 
-namespace SonarLint.VisualStudio.Education.UnitTests
+namespace SonarLint.VisualStudio.Education.UnitTests;
+
+[TestClass]
+public class EducationTests
 {
-    [TestClass]
-    public class EducationTests
+    private readonly SonarCompositeRuleId knownRule = new("repoKey", "ruleKey");
+    private readonly SonarCompositeRuleId unknownRule = new("known", "xxx");
+
+    private ILogger logger;
+    private IRuleHelpToolWindow ruleDescriptionToolWindow;
+    private IRuleHelpXamlBuilder ruleHelpXamlBuilder;
+    private IRuleInfo ruleInfo;
+    private IRuleMetaDataProvider ruleMetadataProvider;
+    private IShowRuleInBrowser showRuleInBrowser;
+    private Education testSubject;
+    private IThreadHandling threadHandling;
+    private IToolWindowService toolWindowService;
+
+    [TestInitialize]
+    public void TestInitialize()
+    {
+        toolWindowService = Substitute.For<IToolWindowService>();
+        ruleMetadataProvider = Substitute.For<IRuleMetaDataProvider>();
+        showRuleInBrowser = Substitute.For<IShowRuleInBrowser>();
+        ruleHelpXamlBuilder = Substitute.For<IRuleHelpXamlBuilder>();
+        ruleDescriptionToolWindow = Substitute.For<IRuleHelpToolWindow>();
+        ruleInfo = Substitute.For<IRuleInfo>();
+        logger = new TestLogger(true);
+        threadHandling = new NoOpThreadHandler();
+        GetRuleInfoAsync(ruleInfo);
+
+        testSubject = new Education(toolWindowService, ruleMetadataProvider, showRuleInBrowser, logger, ruleHelpXamlBuilder, threadHandling);
+    }
+
+    [TestMethod]
+    public void MefCtor_CheckIsExported() =>
+        MefTestHelpers.CheckTypeCanBeImported<Education, IEducation>(
+            MefTestHelpers.CreateExport<IToolWindowService>(),
+            MefTestHelpers.CreateExport<IRuleMetaDataProvider>(),
+            MefTestHelpers.CreateExport<IShowRuleInBrowser>(),
+            MefTestHelpers.CreateExport<IRuleHelpXamlBuilder>(),
+            MefTestHelpers.CreateExport<ILogger>());
+
+    [TestMethod]
+    public void ShowRuleHelp_KnownRule_DocumentIsDisplayedInToolWindow()
+    {
+        var flowDocument = MockFlowDocument();
+        toolWindowService.GetToolWindow<RuleHelpToolWindow, IRuleHelpToolWindow>().Returns(ruleDescriptionToolWindow);
+        // Sanity check - tool window not yet fetched
+        toolWindowService.ReceivedCalls().Should().HaveCount(0);
+
+        // Act
+        testSubject.ShowRuleHelp(knownRule, null, null);
+
+        VerifyGetsRuleInfoForCorrectRuleId(knownRule);
+        VerifyRuleIsDisplayedInIde(flowDocument);
+        VerifyRuleNotShownInBrowser();
+    }
+
+    [TestMethod]
+    public void ShowRuleHelp_FailedToDisplayRule_RuleIsShownInBrowser()
+    {
+        ruleHelpXamlBuilder.When(x => x.Create(ruleInfo, /* todo by SLVS-1630 */ null)).Do(x => throw new Exception("some layout error"));
+        toolWindowService.ClearReceivedCalls(); // Called in the constructor, so need to reset to clear the list of invocations
+
+        testSubject.ShowRuleHelp(knownRule, null, /* todo by SLVS-1630 */ null);
+
+        VerifyGetsRuleInfoForCorrectRuleId(knownRule);
+        VerifyRuleShownInBrowser(knownRule);
+        VerifyAttemptsToBuildRuleButFails();
+    }
+
+    [TestMethod]
+    public void ShowRuleHelp_UnknownRule_RuleIsShownInBrowser()
+    {
+        GetRuleInfoAsync(null);
+        toolWindowService.ClearReceivedCalls(); // Called in the constructor, so need to reset to clear the list of invocations
+
+        testSubject.ShowRuleHelp(unknownRule, null, /* todo by SLVS-1630 */ null);
+
+        VerifyGetsRuleInfoForCorrectRuleId(unknownRule);
+        VerifyRuleShownInBrowser(unknownRule);
+        VerifyNotAttemptsBuildRule();
+    }
+
+    [TestMethod]
+    public void ShowRuleHelp_FilterableIssueProvided_CallsGetRuleInfoForIssue()
     {
-        [TestMethod]
-        public void MefCtor_CheckIsExported()
-        {
-            MefTestHelpers.CheckTypeCanBeImported<Education, IEducation>(
-                MefTestHelpers.CreateExport<IToolWindowService>(),
-                MefTestHelpers.CreateExport<IRuleMetaDataProvider>(),
-                MefTestHelpers.CreateExport<IShowRuleInBrowser>(),
-                MefTestHelpers.CreateExport<IRuleHelpXamlBuilder>(),
-                MefTestHelpers.CreateExport<ILogger>());
-        }
+        var issueId = Guid.NewGuid();
+        GetRuleInfoAsync(null);
 
-        [TestMethod]
-        public void ShowRuleHelp_KnownRule_DocumentIsDisplayedInToolWindow()
-        {
-            var ruleMetaDataProvider = new Mock<IRuleMetaDataProvider>();
-            var ruleId = new SonarCompositeRuleId("repoKey", "ruleKey");
-
-            var ruleInfo = Mock.Of<IRuleInfo>();
-            ruleMetaDataProvider.Setup(x => x.GetRuleInfoAsync(It.IsAny<SonarCompositeRuleId>(), It.IsAny<Guid?>())).ReturnsAsync(ruleInfo);
-
-            var flowDocument = Mock.Of<FlowDocument>();
-            var ruleHelpXamlBuilder = new Mock<IRuleHelpXamlBuilder>();
-            ruleHelpXamlBuilder.Setup(x => x.Create(ruleInfo, /* todo by SLVS-1630 */ null)).Returns(flowDocument);
-
-            var ruleDescriptionToolWindow = new Mock<IRuleHelpToolWindow>();
-
-            var toolWindowService = new Mock<IToolWindowService>();
-            toolWindowService.Setup(x => x.GetToolWindow<RuleHelpToolWindow, IRuleHelpToolWindow>()).Returns(ruleDescriptionToolWindow.Object);
-
-            var showRuleInBrowser = new Mock<IShowRuleInBrowser>();
-            var testSubject = CreateEducation(toolWindowService.Object,
-                ruleMetaDataProvider.Object,
-                showRuleInBrowser.Object,
-                ruleHelpXamlBuilder.Object);
-
-            // Sanity check - tool window not yet fetched
-            toolWindowService.Invocations.Should().HaveCount(0);
-
-            // Act
-            testSubject.ShowRuleHelp(ruleId, null, null);
-
-            ruleMetaDataProvider.Verify(x => x.GetRuleInfoAsync(ruleId, It.IsAny<Guid?>()), Times.Once);
-            ruleHelpXamlBuilder.Verify(x => x.Create(ruleInfo, /* todo by SLVS-1630 */ null), Times.Once);
-            ruleDescriptionToolWindow.Verify(x => x.UpdateContent(flowDocument), Times.Once);
-            toolWindowService.Verify(x => x.Show(RuleHelpToolWindow.ToolWindowId), Times.Once);
-
-            showRuleInBrowser.Invocations.Should().HaveCount(0);
-        }
-
-        [TestMethod]
-        public void ShowRuleHelp_FailedToDisplayRule_RuleIsShownInBrowser()
-        {
-            var toolWindowService = new Mock<IToolWindowService>();
-            var ruleMetadataProvider = new Mock<IRuleMetaDataProvider>();
-            var ruleHelpXamlBuilder = new Mock<IRuleHelpXamlBuilder>();
-            var showRuleInBrowser = new Mock<IShowRuleInBrowser>();
-
-            var ruleId = new SonarCompositeRuleId("repoKey", "ruleKey");
-
-            var ruleInfo = Mock.Of<IRuleInfo>();
-            ruleMetadataProvider.Setup(x => x.GetRuleInfoAsync(It.IsAny<SonarCompositeRuleId>(), It.IsAny<Guid?>())).ReturnsAsync(ruleInfo);
-
-            ruleHelpXamlBuilder.Setup(x => x.Create(ruleInfo, /* todo by SLVS-1630 */ null)).Throws(new Exception("some layout error"));
-
-            var testSubject = CreateEducation(
-                toolWindowService.Object,
-                ruleMetadataProvider.Object,
-                showRuleInBrowser.Object,
-                ruleHelpXamlBuilder.Object);
-
-            toolWindowService.Reset(); // Called in the constructor, so need to reset to clear the list of invocations
-
-            testSubject.ShowRuleHelp(ruleId, null, /* todo by SLVS-1630 */null);
-
-            ruleMetadataProvider.Verify(x => x.GetRuleInfoAsync(ruleId, It.IsAny<Guid?>()), Times.Once);
-            showRuleInBrowser.Verify(x => x.ShowRuleDescription(ruleId), Times.Once);
-
-            // should have attempted to build the rule, but failed
-            ruleHelpXamlBuilder.Invocations.Should().HaveCount(1);
-            toolWindowService.Invocations.Should().HaveCount(1);
-        }
-
-        [TestMethod]
-        public void ShowRuleHelp_UnknownRule_RuleIsShownInBrowser()
-        {
-            var toolWindowService = new Mock<IToolWindowService>();
-            var ruleMetadataProvider = new Mock<IRuleMetaDataProvider>();
-            var ruleHelpXamlBuilder = new Mock<IRuleHelpXamlBuilder>();
-            var showRuleInBrowser = new Mock<IShowRuleInBrowser>();
-
-            var unknownRule = new SonarCompositeRuleId("known", "xxx");
-            ruleMetadataProvider.Setup(x => x.GetRuleInfoAsync(unknownRule, It.IsAny<Guid?>())).ReturnsAsync((IRuleInfo)null);
-
-            var testSubject = CreateEducation(
-                toolWindowService.Object,
-                ruleMetadataProvider.Object,
-                showRuleInBrowser.Object,
-                ruleHelpXamlBuilder.Object);
-
-            toolWindowService.Reset(); // Called in the constructor, so need to reset to clear the list of invocations
-
-            testSubject.ShowRuleHelp(unknownRule, null, /* todo by SLVS-1630 */ null);
-
-            ruleMetadataProvider.Verify(x => x.GetRuleInfoAsync(unknownRule, It.IsAny<Guid?>()), Times.Once);
-            showRuleInBrowser.Verify(x => x.ShowRuleDescription(unknownRule), Times.Once);
-
-            // Should not have attempted to build the rule
-            ruleHelpXamlBuilder.Invocations.Should().HaveCount(0);
-            toolWindowService.Invocations.Should().HaveCount(0);
-        }
-
-        [TestMethod]
-        public void ShowRuleHelp_FilterableIssueProvided_CallsGetRuleInfoForIssue()
-        {
-            var toolWindowService = new Mock<IToolWindowService>();
-            var ruleMetadataProvider = new Mock<IRuleMetaDataProvider>();
-            var ruleHelpXamlBuilder = new Mock<IRuleHelpXamlBuilder>();
-            var showRuleInBrowser = new Mock<IShowRuleInBrowser>();
-            var issueId = Guid.NewGuid();
-            var ruleId = new SonarCompositeRuleId("repoKey", "ruleKey");
-            ruleMetadataProvider.Setup(x => x.GetRuleInfoAsync(ruleId, issueId)).ReturnsAsync((IRuleInfo)null);
-            var testSubject = CreateEducation(
-                toolWindowService.Object,
-                ruleMetadataProvider.Object,
-                showRuleInBrowser.Object,
-                ruleHelpXamlBuilder.Object);
-
-            testSubject.ShowRuleHelp(ruleId,issueId, null);
-
-            ruleMetadataProvider.Verify(x => x.GetRuleInfoAsync(ruleId, issueId), Times.Once);
-        }
-
-        private Education CreateEducation(IToolWindowService toolWindowService = null,
-            IRuleMetaDataProvider ruleMetadataProvider = null,
-            IShowRuleInBrowser showRuleInBrowser = null,
-            IRuleHelpXamlBuilder ruleHelpXamlBuilder = null)
-        {
-            toolWindowService ??= Mock.Of<IToolWindowService>();
-            ruleMetadataProvider ??= Mock.Of<IRuleMetaDataProvider>();
-            showRuleInBrowser ??= Mock.Of<IShowRuleInBrowser>();
-            ruleHelpXamlBuilder ??= Mock.Of<IRuleHelpXamlBuilder>();
-            var logger = new TestLogger(logToConsole: true);
-            var threadHandling = new NoOpThreadHandler();
-
-            return new Education(toolWindowService, ruleMetadataProvider, showRuleInBrowser, logger, ruleHelpXamlBuilder, threadHandling);
-        }
+        testSubject.ShowRuleHelp(knownRule, issueId, null);
+
+        ruleMetadataProvider.Received(1).GetRuleInfoAsync(knownRule, issueId);
+    }
+
+    private void GetRuleInfoAsync(IRuleInfo returnedRuleInfo) => ruleMetadataProvider.GetRuleInfoAsync(Arg.Any<SonarCompositeRuleId>(), Arg.Any<Guid?>()).Returns(returnedRuleInfo);
+
+    private void VerifyGetsRuleInfoForCorrectRuleId(SonarCompositeRuleId ruleId) => ruleMetadataProvider.Received(1).GetRuleInfoAsync(ruleId, Arg.Any<Guid?>());
+
+    private void VerifyRuleShownInBrowser(SonarCompositeRuleId ruleId) => showRuleInBrowser.Received(1).ShowRuleDescription(ruleId);
+
+    private void VerifyRuleNotShownInBrowser() => showRuleInBrowser.ReceivedCalls().Should().HaveCount(0);
+
+    private void VerifyToolWindowShown() => toolWindowService.Received(1).Show(RuleHelpToolWindow.ToolWindowId);
+
+    private void VerifyAttemptsToBuildRuleButFails()
+    {
+        ruleHelpXamlBuilder.ReceivedCalls().Should().HaveCount(1);
+        toolWindowService.ReceivedCalls().Should().HaveCount(1);
+    }
+
+    private void VerifyNotAttemptsBuildRule()
+    {
+        ruleHelpXamlBuilder.ReceivedCalls().Should().HaveCount(0);
+        toolWindowService.ReceivedCalls().Should().HaveCount(0);
+    }
+
+    private void VerifyRuleIsDisplayedInIde(FlowDocument flowDocument)
+    {
+        ruleHelpXamlBuilder.Received(1).Create(ruleInfo, /* todo by SLVS-1630 */ null);
+        ruleDescriptionToolWindow.Received(1).UpdateContent(flowDocument);
+        VerifyToolWindowShown();
+    }
+
+    private FlowDocument MockFlowDocument()
+    {
+        var flowDocument = Substitute.For<FlowDocument>();
+        ruleHelpXamlBuilder.Create(ruleInfo, /* todo by SLVS-1630 */ null).Returns(flowDocument);
+        return flowDocument;
     }
 }
diff --git a/src/Education/Education.cs b/src/Education/Education.cs
index c2bb19d406..8e2737615d 100644
--- a/src/Education/Education.cs
+++ b/src/Education/Education.cs
@@ -21,97 +21,96 @@
 using System.ComponentModel.Composition;
 using Microsoft.VisualStudio.Threading;
 using SonarLint.VisualStudio.Core;
-using SonarLint.VisualStudio.Core.Suppressions;
 using SonarLint.VisualStudio.Education.Rule;
 using SonarLint.VisualStudio.Education.XamlGenerator;
 using SonarLint.VisualStudio.Infrastructure.VS;
 
-namespace SonarLint.VisualStudio.Education
-{
-    [Export(typeof(IEducation))]
-    [PartCreationPolicy(CreationPolicy.Shared)]
-    internal class Education : IEducation
-    {
-        private readonly IToolWindowService toolWindowService;
-        private readonly IRuleMetaDataProvider ruleMetadataProvider;
-        private readonly IRuleHelpXamlBuilder ruleHelpXamlBuilder;
-        private readonly IShowRuleInBrowser showRuleInBrowser;
-        private readonly ILogger logger;
-        private readonly IThreadHandling threadHandling;
+namespace SonarLint.VisualStudio.Education;
 
-        private IRuleHelpToolWindow ruleHelpToolWindow;
+[Export(typeof(IEducation))]
+[PartCreationPolicy(CreationPolicy.Shared)]
+internal class Education : IEducation
+{
+    private readonly ILogger logger;
+    private readonly IRuleHelpXamlBuilder ruleHelpXamlBuilder;
+    private readonly IRuleMetaDataProvider ruleMetadataProvider;
+    private readonly IShowRuleInBrowser showRuleInBrowser;
+    private readonly IThreadHandling threadHandling;
+    private readonly IToolWindowService toolWindowService;
 
-        [ImportingConstructor]
-        public Education(IToolWindowService toolWindowService, IRuleMetaDataProvider ruleMetadataProvider, IShowRuleInBrowser showRuleInBrowser, ILogger logger, IRuleHelpXamlBuilder ruleHelpXamlBuilder)
-            : this(toolWindowService,
-                ruleMetadataProvider,
-                showRuleInBrowser,
-                logger,
-                ruleHelpXamlBuilder,
-                ThreadHandling.Instance) { }
+    private IRuleHelpToolWindow ruleHelpToolWindow;
 
-        internal /* for testing */ Education(IToolWindowService toolWindowService,
-            IRuleMetaDataProvider ruleMetadataProvider,
-            IShowRuleInBrowser showRuleInBrowser,
-            ILogger logger,
-            IRuleHelpXamlBuilder ruleHelpXamlBuilder,
-            IThreadHandling threadHandling)
-        {
-            this.toolWindowService = toolWindowService;
-            this.ruleHelpXamlBuilder = ruleHelpXamlBuilder;
-            this.ruleMetadataProvider = ruleMetadataProvider;
-            this.showRuleInBrowser = showRuleInBrowser;
-            this.logger = logger;
-            this.threadHandling = threadHandling;
-        }
+    [ImportingConstructor]
+    public Education(
+        IToolWindowService toolWindowService,
+        IRuleMetaDataProvider ruleMetadataProvider,
+        IShowRuleInBrowser showRuleInBrowser,
+        ILogger logger,
+        IRuleHelpXamlBuilder ruleHelpXamlBuilder)
+        : this(toolWindowService,
+            ruleMetadataProvider,
+            showRuleInBrowser,
+            logger,
+            ruleHelpXamlBuilder,
+            ThreadHandling.Instance)
+    {
+    }
 
-        public void ShowRuleHelp(SonarCompositeRuleId ruleId, Guid? issueId, string issueContext)
-        {
-            ShowRuleHelpAsync(ruleId, issueId, issueContext).Forget();
-        }
+    internal /* for testing */ Education(
+        IToolWindowService toolWindowService,
+        IRuleMetaDataProvider ruleMetadataProvider,
+        IShowRuleInBrowser showRuleInBrowser,
+        ILogger logger,
+        IRuleHelpXamlBuilder ruleHelpXamlBuilder,
+        IThreadHandling threadHandling)
+    {
+        this.toolWindowService = toolWindowService;
+        this.ruleHelpXamlBuilder = ruleHelpXamlBuilder;
+        this.ruleMetadataProvider = ruleMetadataProvider;
+        this.showRuleInBrowser = showRuleInBrowser;
+        this.logger = logger;
+        this.threadHandling = threadHandling;
+    }
 
-        private async Task ShowRuleHelpAsync(SonarCompositeRuleId ruleId, Guid? issueId, string issueContext)
-        {
-            await threadHandling.SwitchToBackgroundThread();
+    public void ShowRuleHelp(SonarCompositeRuleId ruleId, Guid? issueId, string issueContext) => ShowRuleHelpAsync(ruleId, issueId, issueContext).Forget();
 
-            var ruleInfo = await ruleMetadataProvider.GetRuleInfoAsync(ruleId, issueId);
+    private async Task ShowRuleHelpAsync(SonarCompositeRuleId ruleId, Guid? issueId, string issueContext)
+    {
+        await threadHandling.SwitchToBackgroundThread();
 
-            await threadHandling.RunOnUIThreadAsync(() =>
-            {
-                if (ruleInfo == null)
-                {
-                    showRuleInBrowser.ShowRuleDescription(ruleId);
-                }
-                else
-                {
-                    ShowRuleInIde(ruleInfo, ruleId, issueContext);
-                }
-            });
-        }
+        var ruleInfo = await ruleMetadataProvider.GetRuleInfoAsync(ruleId, issueId);
 
-        private void ShowRuleInIde(IRuleInfo ruleInfo, SonarCompositeRuleId ruleId, string issueContext)
+        await threadHandling.RunOnUIThreadAsync(() =>
         {
-            threadHandling.ThrowIfNotOnUIThread();
-
-            // Lazily fetch the tool window from a UI thread
-            if (ruleHelpToolWindow == null)
+            if (ruleInfo == null)
             {
-                ruleHelpToolWindow = toolWindowService.GetToolWindow<RuleHelpToolWindow, IRuleHelpToolWindow>();
+                showRuleInBrowser.ShowRuleDescription(ruleId);
             }
-
-            try
+            else
             {
-                var flowDocument = ruleHelpXamlBuilder.Create(ruleInfo, issueContext);
+                ShowRuleInIde(ruleInfo, ruleId, issueContext);
+            }
+        });
+    }
 
-                ruleHelpToolWindow.UpdateContent(flowDocument);
+    private void ShowRuleInIde(IRuleInfo ruleInfo, SonarCompositeRuleId ruleId, string issueContext)
+    {
+        threadHandling.ThrowIfNotOnUIThread();
+        // Lazily fetch the tool window from a UI thread
+        ruleHelpToolWindow ??= toolWindowService.GetToolWindow<RuleHelpToolWindow, IRuleHelpToolWindow>();
 
-                toolWindowService.Show(RuleHelpToolWindow.ToolWindowId);
-            }
-            catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex))
-            {
-                logger.WriteLine(string.Format(Resources.ERR_RuleHelpToolWindow_Exception, ex));
-                showRuleInBrowser.ShowRuleDescription(ruleId);
-            }
+        try
+        {
+            var flowDocument = ruleHelpXamlBuilder.Create(ruleInfo, issueContext);
+
+            ruleHelpToolWindow.UpdateContent(flowDocument);
+
+            toolWindowService.Show(RuleHelpToolWindow.ToolWindowId);
+        }
+        catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex))
+        {
+            logger.WriteLine(string.Format(Resources.ERR_RuleHelpToolWindow_Exception, ex));
+            showRuleInBrowser.ShowRuleDescription(ruleId);
         }
     }
 }

From c3b8c3ff7d2653a4f1e97c26f984a5660faa200e Mon Sep 17 00:00:00 2001
From: Gabriela Trutan <gabriela.trutan@sonarsource.com>
Date: Thu, 14 Nov 2024 13:34:14 +0100
Subject: [PATCH 2/6] SLVS-1625 CaYC SonarErrorListEventProcessor and its test
 class

---
 .../UI/ProgressReporterViewModelTests.cs      |   2 +-
 .../SonarErrorListEventProcessorTests.cs      | 142 +++++++++---------
 .../ErrorList/SonarErrorListEventProcessor.cs |  59 +++-----
 .../SonarErrorListEventProcessorProvider.cs   |   1 +
 4 files changed, 96 insertions(+), 108 deletions(-)

diff --git a/src/ConnectedMode.UnitTests/UI/ProgressReporterViewModelTests.cs b/src/ConnectedMode.UnitTests/UI/ProgressReporterViewModelTests.cs
index a815f1ea80..8e36c7a406 100644
--- a/src/ConnectedMode.UnitTests/UI/ProgressReporterViewModelTests.cs
+++ b/src/ConnectedMode.UnitTests/UI/ProgressReporterViewModelTests.cs
@@ -27,8 +27,8 @@ namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.UI;
 [TestClass]
 public class ProgressReporterViewModelTests
 {
-    private ProgressReporterViewModel testSubject;
     private ILogger logger;
+    private ProgressReporterViewModel testSubject;
 
     [TestInitialize]
     public void TestInitialize()
diff --git a/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs b/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs
index 8ce2c980d8..d127f9a25b 100644
--- a/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs
+++ b/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs
@@ -19,95 +19,97 @@
  */
 
 using Microsoft.VisualStudio.Shell.TableControl;
-using Moq;
 using SonarLint.VisualStudio.Core;
 using SonarLint.VisualStudio.Core.Suppressions;
-using SonarLint.VisualStudio.Education.SonarLint.VisualStudio.Education.ErrorList;
+using SonarLint.VisualStudio.Education.ErrorList;
 using SonarLint.VisualStudio.Infrastructure.VS;
 using SonarLint.VisualStudio.TestInfrastructure;
 
-namespace SonarLint.VisualStudio.Education.UnitTests.ErrorList
+namespace SonarLint.VisualStudio.Education.UnitTests.ErrorList;
+
+[TestClass]
+public class SonarErrorListEventProcessorTests
 {
-    [TestClass]
-    public class SonarErrorListEventProcessorTests
+    private readonly TableEntryEventArgs eventArgs = new();
+
+    private IEducation education;
+    private IErrorListHelper errorListHelper;
+    private IFilterableIssue filterableIssue;
+    private ITableEntryHandle handle;
+    private ILogger logger;
+    private SonarErrorListEventProcessor testSubject;
+
+    [TestInitialize]
+    public void TestInitialize()
     {
-        [TestMethod]
-        public void PreprocessNavigateToHelp_NotASonarRule_EventIsNotHandled()
-        {
-            SonarCompositeRuleId ruleId = null;
-            var handle = Mock.Of<ITableEntryHandle>();
-            var errorListHelper = CreateErrorListHelper(isSonarRule: false, ruleId);
+        education = Substitute.For<IEducation>();
+        errorListHelper = Substitute.For<IErrorListHelper>();
+        handle = Substitute.For<ITableEntryHandle>();
+        filterableIssue = Substitute.For<IFilterableIssue>();
+        logger = new TestLogger(true);
 
-            var education = new Mock<IEducation>();
-            var eventArgs = new TableEntryEventArgs();
+        testSubject = new SonarErrorListEventProcessor(education, errorListHelper, logger);
+    }
 
-            var testSubject = CreateTestSubject(education.Object, errorListHelper.Object);
+    [TestMethod]
+    public void PreprocessNavigateToHelp_NotASonarRule_EventIsNotHandled()
+    {
+        SonarCompositeRuleId ruleId = null;
+        MockErrorListHelper(false, ruleId);
 
-            testSubject.PreprocessNavigateToHelp(handle, eventArgs);
+        testSubject.PreprocessNavigateToHelp(handle, eventArgs);
 
-            errorListHelper.Verify(x => x.TryGetRuleId(handle, out ruleId));
-            education.Invocations.Should().HaveCount(0);
-            eventArgs.Handled.Should().BeFalse();
-        }
+        errorListHelper.Received(1).TryGetRuleId(handle, out _);
+        education.ReceivedCalls().Should().HaveCount(0);
+        eventArgs.Handled.Should().BeFalse();
+    }
 
-        [TestMethod]
-        public void PreprocessNavigateToHelp_IsASonarRule_EventIsHandledAndEducationServiceCalled()
-        {
-            SonarCompositeRuleId ruleId;
-            SonarCompositeRuleId.TryParse("cpp:S123", out ruleId);
-            var handle = Mock.Of<ITableEntryHandle>();
-            var errorListHelper = CreateErrorListHelper(isSonarRule: true, ruleId);
+    [TestMethod]
+    public void PreprocessNavigateToHelp_IsASonarRule_EventIsHandledAndEducationServiceCalled()
+    {
+        var ruleId = CreateSonarCompositeRuleId("cpp:S123");
+        MockErrorListHelper(true, ruleId);
 
-            var education = new Mock<IEducation>();
-            var eventArgs = new TableEntryEventArgs();
+        testSubject.PreprocessNavigateToHelp(handle, eventArgs);
 
-            var testSubject = CreateTestSubject(education.Object, errorListHelper.Object);
+        errorListHelper.Received(1).TryGetRuleId(handle, out _);
+        education.ReceivedCalls().Should().HaveCount(1);
+        education.Received(1).ShowRuleHelp(ruleId, null, /* todo by SLVS-1630 */ null);
+        eventArgs.Handled.Should().BeTrue();
+    }
 
-            testSubject.PreprocessNavigateToHelp(handle, eventArgs);
+    [TestMethod]
+    [DataRow(true)]
+    [DataRow(false)]
+    public void PreprocessNavigateToHelp_IsASonarRule_EducationServiceIsCalledWithIssueId(bool getFilterableIssueResult)
+    {
+        var ruleId = CreateSonarCompositeRuleId("cpp:S123");
+        MockErrorListHelper(true, ruleId);
+        MockGetFilterableIssue(getFilterableIssueResult);
 
-            errorListHelper.Verify(x => x.TryGetRuleId(handle, out ruleId));
-            education.Invocations.Should().HaveCount(1);
-            education.Verify(x => x.ShowRuleHelp(ruleId, null, /* todo by SLVS-1630 */ null));
-            eventArgs.Handled.Should().BeTrue();
-        }
+        testSubject.PreprocessNavigateToHelp(handle, new TableEntryEventArgs());
 
-        [TestMethod]
-        [DataRow(true)]
-        [DataRow(false)]
-        public void PreprocessNavigateToHelp_IsASonarRule_EducationServiceIsCalledWithIssueId(bool getFilterableIssueResult)
-        {
-            SonarCompositeRuleId ruleId;
-            IFilterableIssue filterableIssue = new Mock<IFilterableIssue>().Object;
-            SonarCompositeRuleId.TryParse("cpp:S123", out ruleId);
-            var handle = Mock.Of<ITableEntryHandle>();
-            var errorListHelper = CreateErrorListHelper(isSonarRule: true, ruleId);
-            errorListHelper.Setup(x => x.TryGetFilterableIssue(It.IsAny<ITableEntryHandle>(), out filterableIssue)).Returns(getFilterableIssueResult);
-            var education = new Mock<IEducation>();
-            var testSubject = CreateTestSubject(education.Object, errorListHelper.Object);
-
-            testSubject.PreprocessNavigateToHelp(handle, new TableEntryEventArgs());
-
-            education.Invocations.Should().HaveCount(1);
-            education.Verify(x => x.ShowRuleHelp(ruleId, filterableIssue.IssueId, null));
-        }
-
-        private static Mock<IErrorListHelper> CreateErrorListHelper(bool isSonarRule, SonarCompositeRuleId ruleId)
+        education.ReceivedCalls().Should().HaveCount(1);
+        education.Received(1).ShowRuleHelp(ruleId, filterableIssue.IssueId, null);
+    }
+
+    private void MockGetFilterableIssue(bool getFilterableIssueResult) =>
+        errorListHelper.TryGetFilterableIssue(Arg.Any<ITableEntryHandle>(), out _).Returns(callInfo =>
         {
-            var mock = new Mock<IErrorListHelper>();
-            mock.Setup(x => x.TryGetRuleId(It.IsAny<ITableEntryHandle>(), out ruleId)).Returns(isSonarRule);
-            return mock;
-        }
-
-        private static SonarErrorListEventProcessor CreateTestSubject(IEducation educationService = null,
-            IErrorListHelper errorListHelper = null,
-            ILogger logger = null)
+            callInfo[1] = filterableIssue;
+            return getFilterableIssueResult;
+        });
+
+    private void MockErrorListHelper(bool isSonarRule, SonarCompositeRuleId ruleId) =>
+        errorListHelper.TryGetRuleId(Arg.Any<ITableEntryHandle>(), out _).Returns(callInfo =>
         {
-            educationService ??= Mock.Of<IEducation>();
-            errorListHelper ??= Mock.Of<IErrorListHelper>();
-            logger ??= new TestLogger(logToConsole: true);
+            callInfo[1] = ruleId;
+            return isSonarRule;
+        });
 
-            var testSubject = new SonarErrorListEventProcessor(educationService, errorListHelper, logger);
-            return testSubject;
-        }
+    private static SonarCompositeRuleId CreateSonarCompositeRuleId(string errorListErrorCode)
+    {
+        SonarCompositeRuleId.TryParse(errorListErrorCode, out var ruleId);
+        return ruleId;
     }
 }
diff --git a/src/Education/ErrorList/SonarErrorListEventProcessor.cs b/src/Education/ErrorList/SonarErrorListEventProcessor.cs
index 5c3828a5a2..abfd9c6cb7 100644
--- a/src/Education/ErrorList/SonarErrorListEventProcessor.cs
+++ b/src/Education/ErrorList/SonarErrorListEventProcessor.cs
@@ -23,50 +23,35 @@
 using SonarLint.VisualStudio.Core;
 using SonarLint.VisualStudio.Infrastructure.VS;
 
-namespace SonarLint.VisualStudio.Education
+namespace SonarLint.VisualStudio.Education.ErrorList;
+
+/// <summary>
+///     Processor class that lets us handle WPF events from the Error List
+/// </summary>
+internal class SonarErrorListEventProcessor(IEducation educationService, IErrorListHelper errorListHelper, ILogger logger) : TableControlEventProcessorBase
 {
-    namespace SonarLint.VisualStudio.Education.ErrorList
+    public override void PreprocessNavigateToHelp(
+        ITableEntryHandle entry,
+        TableEntryEventArgs e)
     {
-        /// <summary>
-        /// Proccessor class that lets us handle WPF events from the Error List
-        /// </summary>
-        internal class SonarErrorListEventProcessor : TableControlEventProcessorBase
-        {
-            private readonly IEducation educationService;
-            private readonly IErrorListHelper errorListHelper;
-            private readonly ILogger logger;
-
-            public SonarErrorListEventProcessor(IEducation educationService, IErrorListHelper errorListHelper, ILogger logger)
-            {
-                this.educationService = educationService;
-                this.errorListHelper = errorListHelper;
-                this.logger = logger;
-            }
-
-            public override void PreprocessNavigateToHelp(
-               ITableEntryHandle entry,
-               TableEntryEventArgs e)
-            {
-                // If the user is navigating to help for one of the Sonar rules,
-                // show our rule description tool window
+        // If the user is navigating to help for one of the Sonar rules,
+        // show our rule description tool window
 
-                Requires.NotNull(entry, nameof(entry));
+        Requires.NotNull(entry, nameof(entry));
 
-                bool handled = false;
+        var handled = false;
 
-                if (errorListHelper.TryGetRuleId(entry, out var ruleId))
-                {
-                    errorListHelper.TryGetFilterableIssue(entry, out var filterableIssue);
-                    logger.LogVerbose(Resources.ErrorList_Processor_SonarRuleDetected, ruleId);
-
-                    educationService.ShowRuleHelp(ruleId, filterableIssue?.IssueId, /* todo by SLVS-1630 */null);
+        if (errorListHelper.TryGetRuleId(entry, out var ruleId))
+        {
+            errorListHelper.TryGetFilterableIssue(entry, out var filterableIssue);
+            logger.LogVerbose(Resources.ErrorList_Processor_SonarRuleDetected, ruleId);
 
-                    // Mark the event as handled to stop the normal VS "show help in browser" behaviour
-                    handled = true;
-                }
+            educationService.ShowRuleHelp(ruleId, filterableIssue?.IssueId, /* todo by SLVS-1630 */null);
 
-                e.Handled = handled;
-            }
+            // Mark the event as handled to stop the normal VS "show help in browser" behaviour
+            handled = true;
         }
+
+        e.Handled = handled;
     }
 }
diff --git a/src/Education/ErrorList/SonarErrorListEventProcessorProvider.cs b/src/Education/ErrorList/SonarErrorListEventProcessorProvider.cs
index c385a8fa24..64f35a551c 100644
--- a/src/Education/ErrorList/SonarErrorListEventProcessorProvider.cs
+++ b/src/Education/ErrorList/SonarErrorListEventProcessorProvider.cs
@@ -22,6 +22,7 @@
 using Microsoft.VisualStudio.Shell.TableControl;
 using Microsoft.VisualStudio.Utilities;
 using SonarLint.VisualStudio.Core;
+using SonarLint.VisualStudio.Education.ErrorList;
 using SonarLint.VisualStudio.Infrastructure.VS;
 
 namespace SonarLint.VisualStudio.Education

From 01fe6315778f339e2935c32c29db5cbd06b76078 Mon Sep 17 00:00:00 2001
From: Gabriela Trutan <gabriela.trutan@sonarsource.com>
Date: Thu, 14 Nov 2024 14:33:34 +0100
Subject: [PATCH 3/6] SLVS-1625 CaYC SLCoreRuleMetaDataProvider and its test
 class

---
 .../Rule/SLCoreRuleMetaDataProviderTests.cs   | 274 +++++++-----------
 .../Rule/SLCoreRuleMetaDataProvider.cs        |  25 +-
 2 files changed, 119 insertions(+), 180 deletions(-)

diff --git a/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs b/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs
index 676b139045..b7fdd3b2ce 100644
--- a/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs
+++ b/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs
@@ -18,24 +18,50 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-using Moq;
+using NSubstitute.ExceptionExtensions;
 using SonarLint.VisualStudio.Core;
 using SonarLint.VisualStudio.Education.Rule;
 using SonarLint.VisualStudio.SLCore.Core;
-using SonarLint.VisualStudio.SLCore.Protocol;
+using SonarLint.VisualStudio.SLCore.Service.Issue;
+using SonarLint.VisualStudio.SLCore.Service.Issue.Models;
 using SonarLint.VisualStudio.SLCore.Service.Rules;
 using SonarLint.VisualStudio.SLCore.Service.Rules.Models;
 using SonarLint.VisualStudio.SLCore.State;
 using SonarLint.VisualStudio.TestInfrastructure;
-using SonarLint.VisualStudio.SLCore.Service.Issue;
-using SonarLint.VisualStudio.SLCore.Service.Issue.Models;
 
 namespace SonarLint.VisualStudio.Education.UnitTests.Rule;
 
 [TestClass]
 public class SLCoreRuleMetaDataProviderTests
 {
-    private static readonly SonarCompositeRuleId CompositeRuleId = new("rule", "key1");
+    private readonly SonarCompositeRuleId compositeRuleId = new("rule", "key1");
+    private readonly ConfigurationScope configurationScope = new("id");
+    private readonly RuleInfo defaultRuleInfo = new(default, default, default, default, default, default, default, default);
+    private readonly EffectiveIssueDetailsDto effectiveIssueDetailsDto = new(default, default, default, default, default, default, default, default);
+    private readonly string errorMessage = "my message";
+    private readonly Guid issueId = Guid.NewGuid();
+
+    private IActiveConfigScopeTracker configScopeTrackerMock;
+    private IIssueSLCoreService issueServiceMock;
+    private TestLogger logger;
+    private IRuleInfoConverter ruleInfoConverter;
+    private IRulesSLCoreService rulesServiceMock;
+    private ISLCoreServiceProvider serviceProviderMock;
+    private SLCoreRuleMetaDataProvider testSubject;
+
+    [TestInitialize]
+    public void TestInitialize()
+    {
+        serviceProviderMock = Substitute.For<ISLCoreServiceProvider>();
+        configScopeTrackerMock = Substitute.For<IActiveConfigScopeTracker>();
+        issueServiceMock = Substitute.For<IIssueSLCoreService>();
+        rulesServiceMock = Substitute.For<IRulesSLCoreService>();
+        ruleInfoConverter = Substitute.For<IRuleInfoConverter>();
+        logger = new TestLogger();
+
+        testSubject = new SLCoreRuleMetaDataProvider(serviceProviderMock, configScopeTrackerMock, ruleInfoConverter, logger);
+        MockupServices();
+    }
 
     [TestMethod]
     public void MefCtor_CheckIsExported() =>
@@ -51,12 +77,9 @@ public void MefCtor_CheckIsExported() =>
     [TestMethod]
     public async Task GetRuleInfoAsync_NoActiveScope_ReturnsNull()
     {
-        var testSubject =
-            CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out var logger);
-        SetUpServiceProvider(serviceProviderMock, out _);
-        SetUpConfigScopeTracker(configScopeTrackerMock, null);
+        SetUpConfigScopeTracker(null);
 
-        var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId);
+        var ruleInfo = await testSubject.GetRuleInfoAsync(compositeRuleId);
 
         ruleInfo.Should().BeNull();
         logger.AssertNoOutputMessages();
@@ -65,10 +88,9 @@ public async Task GetRuleInfoAsync_NoActiveScope_ReturnsNull()
     [TestMethod]
     public async Task GetRuleInfoAsync_ServiceUnavailable_ReturnsNull()
     {
-        var testSubject = CreateTestSubject(out _, out var configScopeTrackerMock, out var logger);
-        SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("id"));
+        SetUpRuleServiceProvider(false);
 
-        var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId);
+        var ruleInfo = await testSubject.GetRuleInfoAsync(compositeRuleId);
 
         ruleInfo.Should().BeNull();
         logger.AssertNoOutputMessages();
@@ -77,209 +99,137 @@ public async Task GetRuleInfoAsync_ServiceUnavailable_ReturnsNull()
     [TestMethod]
     public void GetRuleInfoAsync_ServiceThrows_ReturnsNullAndLogs()
     {
-        var testSubject =
-            CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out var logger);
-        SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("id"));
-        SetUpServiceProvider(serviceProviderMock, out var rulesServiceMock);
-        rulesServiceMock
-            .Setup(x => x.GetEffectiveRuleDetailsAsync(It.IsAny<GetEffectiveRuleDetailsParams>()))
-            .ThrowsAsync(new Exception("my message"));
+        MockGetEffectiveRuleDetailsAsyncThrows();
 
-        var act = () => testSubject.GetRuleInfoAsync(CompositeRuleId);
+        var act = () => testSubject.GetRuleInfoAsync(compositeRuleId);
 
         act.Should().NotThrow();
-        logger.AssertPartialOutputStringExists("my message");
+        logger.AssertPartialOutputStringExists(errorMessage);
     }
 
     [TestMethod]
     public async Task GetRuleInfoAsync_ForIssue_NoActiveScope_ReturnsNull()
     {
-        var testSubject =
-            CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out var logger);
-        SetUpIssueServiceProvider(serviceProviderMock, out _);
-        SetUpConfigScopeTracker(configScopeTrackerMock, null);
-        var issueId = Guid.NewGuid();
+        SetUpConfigScopeTracker(null);
 
-        var ruleInfo = await testSubject.GetRuleInfoAsync(default,issueId);
+        var ruleInfo = await testSubject.GetRuleInfoAsync(compositeRuleId, issueId);
 
         ruleInfo.Should().BeNull();
         logger.AssertNoOutputMessages();
     }
 
     [TestMethod]
-    public async Task GetRuleInfoAsync_ForIssue_ServiceUnavailable_ReturnsNull()
+    public async Task GetRuleInfoAsync_ForIssue_IssueServiceUnavailable_ReturnsResultFromRulesService()
     {
-        var testSubject = CreateTestSubject(out _, out var configScopeTrackerMock, out var logger);
-        SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("id"));
+        SetUpIssueServiceProvider(false);
+        MockGetEffectiveRuleDetailsAsync(compositeRuleId.ToString(), configurationScope.Id);
 
-        var ruleInfo = await testSubject.GetRuleInfoAsync(default,Guid.NewGuid());
+        var ruleInfo = await testSubject.GetRuleInfoAsync(compositeRuleId, issueId);
 
-        ruleInfo.Should().BeNull();
+        ruleInfo.Should().NotBeNull();
         logger.AssertNoOutputMessages();
+        await VerifyGetRuleDetailsWasCalled(compositeRuleId.ToString());
     }
 
     [TestMethod]
-    public void GetRuleInfoAsync_ForIssue_ServiceThrows_ReturnsNullAndLogs()
+    public void GetRuleInfoAsync_ForIssue_IssueServiceThrows_ReturnsNullAndLogs()
     {
-        var testSubject =
-            CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out var logger);
-        SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("id"));
-        SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock);
-        issueServiceMock
-            .Setup(x => x.GetEffectiveIssueDetailsAsync(It.IsAny<GetEffectiveIssueDetailsParams>()))
-            .ThrowsAsync(new Exception("my message"));
+        MockGetEffectiveIssueDetailsAsyncThrows();
 
-        var act = () => testSubject.GetRuleInfoAsync(default,Guid.NewGuid());
+        var act = () => testSubject.GetRuleInfoAsync(compositeRuleId, issueId);
 
         act.Should().NotThrow();
-        logger.AssertPartialOutputStringExists("my message");
+        logger.AssertPartialOutputStringExists(errorMessage);
     }
 
     [TestMethod]
-    public async Task GetRuleInfoAsync_FilterableIssueNull_CallsGetEffectiveRuleDetailsAsync()
+    public async Task GetRuleInfoAsync_IssueIdNull_CallsGetEffectiveRuleDetailsAsync()
     {
-        var testSubject =
-            CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _);
-        SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock);
-        SetUpServiceProvider(serviceProviderMock, out var rulesServiceMock);
-        SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope"));
-
-        await testSubject.GetRuleInfoAsync(CompositeRuleId, null);
+        await testSubject.GetRuleInfoAsync(compositeRuleId, null);
 
-        rulesServiceMock.Verify(x => x.GetEffectiveRuleDetailsAsync(It.Is<GetEffectiveRuleDetailsParams>(p => p.ruleKey == CompositeRuleId.ToString())), Times.Once);
-        issueServiceMock.Verify(x => x.GetEffectiveIssueDetailsAsync(It.IsAny<GetEffectiveIssueDetailsParams>()), Times.Never);
+        await VerifyGetRuleDetailsWasCalled(compositeRuleId.ToString());
+        await VerifyIssueDetailsWasNotCalled();
     }
 
     [TestMethod]
-    public async Task GetRuleInfoAsync_FilterableIssueIdNull_CallsGetEffectiveRuleDetailsAsync()
+    public async Task GetRuleInfoAsync_IssueIdNotNull_CallsGetEffectiveIssueDetailsAsync()
     {
-        var testSubject =
-            CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _);
-        SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock);
-        SetUpServiceProvider(serviceProviderMock, out var rulesServiceMock);
-        SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope"));
-        Guid? issueId = null;
+        MockGetEffectiveIssueDetailsAsync(issueId, configurationScope.Id);
 
-        await testSubject.GetRuleInfoAsync(CompositeRuleId, issueId);
-
-        rulesServiceMock.Verify(x => x.GetEffectiveRuleDetailsAsync(It.Is<GetEffectiveRuleDetailsParams>(p => p.ruleKey == CompositeRuleId.ToString())), Times.Once);
-        issueServiceMock.Verify(x => x.GetEffectiveIssueDetailsAsync(It.IsAny<GetEffectiveIssueDetailsParams>()), Times.Never);
-    }
+        await testSubject.GetRuleInfoAsync(compositeRuleId, issueId);
 
-    [TestMethod]
-    public async Task GetRuleInfoAsync_FilterableIssueIdNotNull_CallsGetEffectiveIssueDetailsAsync()
-    {
-        var configScopeId = "configscope";
-        var issueId = Guid.NewGuid();
-        var testSubject = CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _);
-        SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock);
-        SetUpServiceProvider(serviceProviderMock, out var rulesServiceMock);
-        SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope(configScopeId));
-        SetupIssuesService(issueServiceMock, issueId, configScopeId, CreateEffectiveIssueDetailsDto(new MQRModeDetails(default, default)));
-
-        await testSubject.GetRuleInfoAsync(CompositeRuleId, issueId);
-
-        rulesServiceMock.Verify(x => x.GetEffectiveRuleDetailsAsync(It.IsAny<GetEffectiveRuleDetailsParams>()), Times.Never);
-        issueServiceMock.Verify(x => x.GetEffectiveIssueDetailsAsync(It.Is<GetEffectiveIssueDetailsParams>(p => p.issueId == issueId)), Times.Once);
+        await VerifyRuleDetailsWasNotCalled();
+        await VerifyGetIssueDetailsWasCalled(issueId);
     }
 
     [TestMethod]
     public async Task GetRuleInfoAsync_GetEffectiveIssueDetailsAsyncThrows_CallsGetEffectiveRuleDetailsAsync()
     {
-        var testSubject =
-            CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _);
-        SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock);
-        SetUpServiceProvider(serviceProviderMock, out var rulesServiceMock);
-        SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope"));
-        var issueId = Guid.NewGuid();
-        issueServiceMock
-            .Setup(x => x.GetEffectiveIssueDetailsAsync(It.IsAny<GetEffectiveIssueDetailsParams>()))
-            .ThrowsAsync(new Exception("my message"));
-
-        await testSubject.GetRuleInfoAsync(CompositeRuleId, issueId);
-
-        rulesServiceMock.Verify(x => x.GetEffectiveRuleDetailsAsync(It.Is<GetEffectiveRuleDetailsParams>(p => p.ruleKey == CompositeRuleId.ToString())), Times.Once);
-        issueServiceMock.Verify(x => x.GetEffectiveIssueDetailsAsync(It.Is<GetEffectiveIssueDetailsParams>(p => p.issueId == issueId)), Times.Once);
+        MockGetEffectiveIssueDetailsAsyncThrows();
+
+        await testSubject.GetRuleInfoAsync(compositeRuleId, issueId);
+
+        await VerifyGetRuleDetailsWasCalled(compositeRuleId.ToString());
+        await VerifyGetIssueDetailsWasCalled(issueId);
     }
 
     [TestMethod]
     public async Task GetRuleInfoAsync_BothServicesThrow_ReturnsNull()
     {
-        var testSubject =
-            CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _);
-        SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock);
-        SetUpServiceProvider(serviceProviderMock, out var rulesServiceMock);
-        SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope"));
-        var issueId = Guid.NewGuid();
-        issueServiceMock
-            .Setup(x => x.GetEffectiveIssueDetailsAsync(It.IsAny<GetEffectiveIssueDetailsParams>()))
-            .ThrowsAsync(new Exception("my message"));
-        rulesServiceMock
-            .Setup(x => x.GetEffectiveRuleDetailsAsync(It.IsAny<GetEffectiveRuleDetailsParams>()))
-            .ThrowsAsync(new Exception("my message"));
-
-        var result = await testSubject.GetRuleInfoAsync(CompositeRuleId, issueId);
+        MockGetEffectiveIssueDetailsAsyncThrows();
+        MockGetEffectiveRuleDetailsAsyncThrows();
+
+        var result = await testSubject.GetRuleInfoAsync(compositeRuleId, issueId);
 
         result.Should().BeNull();
-        rulesServiceMock.Verify(x => x.GetEffectiveRuleDetailsAsync(It.Is<GetEffectiveRuleDetailsParams>(p => p.ruleKey == CompositeRuleId.ToString())), Times.Once);
-        issueServiceMock.Verify(x => x.GetEffectiveIssueDetailsAsync(It.Is<GetEffectiveIssueDetailsParams>(p => p.issueId == issueId)), Times.Once);
+        await VerifyGetRuleDetailsWasCalled(compositeRuleId.ToString());
+        await VerifyGetIssueDetailsWasCalled(issueId);
     }
 
-    private static void SetUpConfigScopeTracker(
-        Mock<IActiveConfigScopeTracker> configScopeTrackerMock,
-        ConfigurationScope scope) =>
-        configScopeTrackerMock.SetupGet(x => x.Current).Returns(scope);
-
-    private static void SetupIssuesService(
-        Mock<IIssueSLCoreService> issuesServiceMock,
-        Guid id,
-        string configScopeId,
-        EffectiveIssueDetailsDto response) =>
-        issuesServiceMock
-            .Setup(r => r.GetEffectiveIssueDetailsAsync(It.Is<GetEffectiveIssueDetailsParams>(p => p.configurationScopeId == configScopeId && p.issueId == id)))
-            .ReturnsAsync(new GetEffectiveIssueDetailsResponse(response));
-
-    private static void SetUpServiceProvider(
-        Mock<ISLCoreServiceProvider> serviceProviderMock,
-        out Mock<IRulesSLCoreService> rulesServiceMock)
-    {
-        rulesServiceMock = new Mock<IRulesSLCoreService>();
-        var rulesService = rulesServiceMock.Object;
-        serviceProviderMock.Setup(x => x.TryGetTransientService(out rulesService)).Returns(true);
-    }
+    private void SetUpConfigScopeTracker(ConfigurationScope scope) => configScopeTrackerMock.Current.Returns(scope);
 
-    private static void SetUpIssueServiceProvider(
-        Mock<ISLCoreServiceProvider> serviceProviderMock,
-        out Mock<IIssueSLCoreService> rulesServiceMock)
-    {
-        rulesServiceMock = new Mock<IIssueSLCoreService>();
-        var rulesService = rulesServiceMock.Object;
-        serviceProviderMock.Setup(x => x.TryGetTransientService(out rulesService)).Returns(true);
-    }
+    private void MockGetEffectiveIssueDetailsAsyncThrows() => issueServiceMock.GetEffectiveIssueDetailsAsync(Arg.Any<GetEffectiveIssueDetailsParams>()).ThrowsAsync(new Exception(errorMessage));
+
+    private void MockGetEffectiveRuleDetailsAsyncThrows() => rulesServiceMock.GetEffectiveRuleDetailsAsync(Arg.Any<GetEffectiveRuleDetailsParams>()).ThrowsAsync(new Exception(errorMessage));
+
+    private void MockGetEffectiveIssueDetailsAsync(Guid id, string configScopeId) =>
+        issueServiceMock.GetEffectiveIssueDetailsAsync(Arg.Is<GetEffectiveIssueDetailsParams>(x => x.configurationScopeId == configScopeId && x.issueId == id))
+            .Returns(new GetEffectiveIssueDetailsResponse(effectiveIssueDetailsDto));
+
+    private void MockGetEffectiveRuleDetailsAsync(string ruleKey, string configScopeId) =>
+        rulesServiceMock.GetEffectiveRuleDetailsAsync(Arg.Is<GetEffectiveRuleDetailsParams>(x => x.configurationScopeId == configScopeId && x.ruleKey == ruleKey))
+            .Returns(new GetEffectiveRuleDetailsResponse(default));
 
-    private static SLCoreRuleMetaDataProvider CreateTestSubject(
-        out Mock<ISLCoreServiceProvider> serviceProviderMock,
-        out Mock<IActiveConfigScopeTracker> configScopeTrackerMock,
-        out TestLogger logger)
+    private void SetUpRuleServiceProvider(bool result) =>
+        serviceProviderMock.TryGetTransientService(out Arg.Any<IRulesSLCoreService>()).Returns(callInfo =>
+        {
+            callInfo[0] = rulesServiceMock;
+            return result;
+        });
+
+    private void SetUpIssueServiceProvider(bool result) =>
+        serviceProviderMock.TryGetTransientService(out Arg.Any<IIssueSLCoreService>()).Returns(callInfo =>
+        {
+            callInfo[0] = issueServiceMock;
+            return result;
+        });
+
+    private void MockupServices()
     {
-        serviceProviderMock = new Mock<ISLCoreServiceProvider>();
-        configScopeTrackerMock = new Mock<IActiveConfigScopeTracker>();
-        configScopeTrackerMock = new Mock<IActiveConfigScopeTracker>();
-        var ruleInfoConverter = new Mock<IRuleInfoConverter>();
-        ruleInfoConverter.Setup(x => x.Convert(It.IsAny<IRuleDetails>())).Returns(new RuleInfo(default, default, default, default, default, default, default, default));
-        logger = new TestLogger();
-        return new SLCoreRuleMetaDataProvider(serviceProviderMock.Object, configScopeTrackerMock.Object, ruleInfoConverter.Object, logger);
+        MockRuleInfoConverter();
+        SetUpIssueServiceProvider(true);
+        SetUpRuleServiceProvider(true);
+        SetUpConfigScopeTracker(configurationScope);
     }
 
-    private static EffectiveIssueDetailsDto CreateEffectiveIssueDetailsDto(Either<StandardModeDetails, MQRModeDetails> severityDetails,
-        Either<RuleMonolithicDescriptionDto, RuleSplitDescriptionDto> description = default) =>
-        new(
-            default,
-            default,
-            default,
-            default,
-            description,
-            default,
-            severityDetails, 
-            default);
+    private void MockRuleInfoConverter() => ruleInfoConverter.Convert(Arg.Any<IRuleDetails>()).Returns(defaultRuleInfo);
+
+    private async Task VerifyGetIssueDetailsWasCalled(Guid id) => await issueServiceMock.Received(1).GetEffectiveIssueDetailsAsync(Arg.Is<GetEffectiveIssueDetailsParams>(x => x.issueId == id));
+
+    private async Task VerifyGetRuleDetailsWasCalled(string ruleKey) =>
+        await rulesServiceMock.Received(1).GetEffectiveRuleDetailsAsync(Arg.Is<GetEffectiveRuleDetailsParams>(x => x.ruleKey == ruleKey));
+
+    private async Task VerifyIssueDetailsWasNotCalled() => await issueServiceMock.DidNotReceive().GetEffectiveIssueDetailsAsync(Arg.Any<GetEffectiveIssueDetailsParams>());
+
+    private async Task VerifyRuleDetailsWasNotCalled() => await rulesServiceMock.DidNotReceive().GetEffectiveRuleDetailsAsync(Arg.Any<GetEffectiveRuleDetailsParams>());
 }
diff --git a/src/Education/Rule/SLCoreRuleMetaDataProvider.cs b/src/Education/Rule/SLCoreRuleMetaDataProvider.cs
index 8986f656e9..22f2231e6c 100644
--- a/src/Education/Rule/SLCoreRuleMetaDataProvider.cs
+++ b/src/Education/Rule/SLCoreRuleMetaDataProvider.cs
@@ -29,25 +29,14 @@ namespace SonarLint.VisualStudio.Education.Rule;
 
 [Export(typeof(IRuleMetaDataProvider))]
 [PartCreationPolicy(CreationPolicy.Shared)]
-internal class SLCoreRuleMetaDataProvider : IRuleMetaDataProvider
+[method: ImportingConstructor]
+internal class SLCoreRuleMetaDataProvider(
+    ISLCoreServiceProvider slCoreServiceProvider,
+    IActiveConfigScopeTracker activeConfigScopeTracker,
+    IRuleInfoConverter ruleInfoConverter,
+    ILogger logger)
+    : IRuleMetaDataProvider
 {
-    private readonly IActiveConfigScopeTracker activeConfigScopeTracker;
-    private readonly IRuleInfoConverter ruleInfoConverter;
-    private readonly ILogger logger;
-    private readonly ISLCoreServiceProvider slCoreServiceProvider;
-
-    [ImportingConstructor]
-    public SLCoreRuleMetaDataProvider(ISLCoreServiceProvider slCoreServiceProvider,
-        IActiveConfigScopeTracker activeConfigScopeTracker,
-        IRuleInfoConverter ruleInfoConverter,
-        ILogger logger)
-    {
-        this.slCoreServiceProvider = slCoreServiceProvider;
-        this.activeConfigScopeTracker = activeConfigScopeTracker;
-        this.ruleInfoConverter = ruleInfoConverter;
-        this.logger = logger;
-    }
-
     /// <inheritdoc />
     public async Task<IRuleInfo> GetRuleInfoAsync(SonarCompositeRuleId ruleId, Guid? issueId = null)
     {

From 0c8ba8d95447012ebfcf0ea76f04a2f10e1c2ec6 Mon Sep 17 00:00:00 2001
From: Gabriela Trutan <gabriela.trutan@sonarsource.com>
Date: Thu, 14 Nov 2024 15:42:17 +0100
Subject: [PATCH 4/6] SLVS-1625 CaYC ErrorListHelper and its test class

---
 .../ErrorListHelperTests.cs                   | 738 ++++++++----------
 src/Infrastructure.VS/ErrorListHelper.cs      | 288 ++++---
 2 files changed, 479 insertions(+), 547 deletions(-)

diff --git a/src/Infrastructure.VS.UnitTests/ErrorListHelperTests.cs b/src/Infrastructure.VS.UnitTests/ErrorListHelperTests.cs
index ec70d46d49..4a9734cce4 100644
--- a/src/Infrastructure.VS.UnitTests/ErrorListHelperTests.cs
+++ b/src/Infrastructure.VS.UnitTests/ErrorListHelperTests.cs
@@ -22,538 +22,480 @@
 using Microsoft.VisualStudio.Shell.Interop;
 using Microsoft.VisualStudio.Shell.TableControl;
 using Microsoft.VisualStudio.Shell.TableManager;
-using Moq;
 using SonarLint.VisualStudio.IssueVisualization.Models;
 
-namespace SonarLint.VisualStudio.Infrastructure.VS.UnitTests
+namespace SonarLint.VisualStudio.Infrastructure.VS.UnitTests;
+
+[TestClass]
+public class ErrorListHelperTests
 {
-    [TestClass]
-    public class ErrorListHelperTests
+    private IAnalysisIssueVisualization issueMock;
+    private ErrorListHelper testSubject;
+    private IVsUIServiceOperation vsUiServiceOperation;
+
+    [TestInitialize]
+    public void TestInitialize()
     {
-        internal enum TestVsSuppressionState
-        {
-            Active,
-            Suppressed,
-            NotApplicable,
-        }
+        vsUiServiceOperation = Substitute.For<IVsUIServiceOperation>();
+        issueMock = Substitute.For<IAnalysisIssueVisualization>();
 
-        [TestMethod]
-        public void MefCtor_CheckIsExported()
-        {
-            MefTestHelpers.CheckTypeCanBeImported<ErrorListHelper, IErrorListHelper>(
-                MefTestHelpers.CreateExport<IVsUIServiceOperation>());
-        }
+        testSubject = new ErrorListHelper(vsUiServiceOperation);
+    }
 
-        [TestMethod]
-        public void MefCtor_CheckIsSingleton()
-        => MefTestHelpers.CheckIsSingletonMefComponent<ErrorListHelper>();
+    [TestMethod]
+    public void MefCtor_CheckIsExported() =>
+        MefTestHelpers.CheckTypeCanBeImported<ErrorListHelper, IErrorListHelper>(
+            MefTestHelpers.CreateExport<IVsUIServiceOperation>());
 
-        [TestMethod]
-        public void MefCtor_DoesNotCallAnyServices()
-        {
-            var serviceOp = new Mock<IVsUIServiceOperation>();
+    [TestMethod]
+    public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent<ErrorListHelper>();
 
-            _ = new ErrorListHelper(serviceOp.Object);
+    [TestMethod]
+    public void MefCtor_DoesNotCallAnyServices() =>
+        // The MEF constructor should be free-threaded, which it will be if
+        // it doesn't make any external calls.
+        vsUiServiceOperation.ReceivedCalls().Should().BeEmpty();
 
-            // The MEF constructor should be free-threaded, which it will be if
-            // it doesn't make any external calls.
-            serviceOp.Invocations.Should().BeEmpty();
-        }
-
-        [TestMethod]
-        public void TryGetIssueFromSelectedRow_SingleSonarIssue_IssueReturned()
-        {
-            var issueMock = Mock.Of<IAnalysisIssueVisualization>();
-            var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
+    [TestMethod]
+    public void TryGetIssueFromSelectedRow_SingleSonarIssue_IssueReturned()
+    {
+        var issueHandle = CreateIssueHandle(111,
+            new Dictionary<string, object>
             {
                 { StandardTableKeyNames.BuildTool, "SonarLint" },
-                { StandardTableKeyNames.ErrorCode, "javascript:S333"},
+                { StandardTableKeyNames.ErrorCode, "javascript:S333" },
                 { SonarLintTableControlConstants.IssueVizColumnName, issueMock }
             });
-            var errorList = CreateErrorList(issueHandle);
-            var serviceProvider = CreateServiceOperation(errorList);
+        MockErrorList(issueHandle);
 
-            var testSubject = new ErrorListHelper(serviceProvider);
-            bool result = testSubject.TryGetIssueFromSelectedRow(out var issue);
+        var result = testSubject.TryGetIssueFromSelectedRow(out var issue);
 
-            result.Should().BeTrue();
-            issue.Should().BeSameAs(issueMock);
-        }
+        result.Should().BeTrue();
+        issue.Should().BeSameAs(issueMock);
+    }
 
-        [TestMethod]
-        public void TryGetIssueFromSelectedRow_SingleItemButNoAnalysisIssue_IssueNotReturned()
-        {
-            var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
+    [TestMethod]
+    public void TryGetIssueFromSelectedRow_SingleItemButNoAnalysisIssue_IssueNotReturned()
+    {
+        var issueHandle = CreateIssueHandle(111,
+            new Dictionary<string, object>
             {
                 { StandardTableKeyNames.BuildTool, "SonarLint" },
-                { StandardTableKeyNames.ErrorCode, "javascript:S333"},
+                { StandardTableKeyNames.ErrorCode, "javascript:S333" },
                 { SonarLintTableControlConstants.IssueVizColumnName, null }
             });
-            var errorList = CreateErrorList(issueHandle);
-            var serviceProvider = CreateServiceOperation(errorList);
+        MockErrorList(issueHandle);
 
-            var testSubject = new ErrorListHelper(serviceProvider);
-            var result = testSubject.TryGetIssueFromSelectedRow(out _);
+        var result = testSubject.TryGetIssueFromSelectedRow(out _);
 
-            result.Should().BeFalse();
-        }
+        result.Should().BeFalse();
+    }
 
-        [TestMethod]
-        public void TryGetIssueFromSelectedRow_MultipleItemsSelected_IssueNotReturned()
-        {
-            var cppIssueHandle = CreateIssueHandle(111, new Dictionary<string, object>
+    [TestMethod]
+    public void TryGetIssueFromSelectedRow_MultipleItemsSelected_IssueNotReturned()
+    {
+        var cppIssueHandle = CreateIssueHandle(111,
+            new Dictionary<string, object>
             {
                 { StandardTableKeyNames.BuildTool, "SonarLint" },
                 { StandardTableKeyNames.ErrorCode, "cpp:S222" },
-                { SonarLintTableControlConstants.IssueVizColumnName, Mock.Of<IAnalysisIssueVisualization>() }
+                { SonarLintTableControlConstants.IssueVizColumnName, Substitute.For<IAnalysisIssueVisualization>() }
             });
-            var jsIssueHandle = CreateIssueHandle(222, new Dictionary<string, object>
+        var jsIssueHandle = CreateIssueHandle(222,
+            new Dictionary<string, object>
             {
                 { StandardTableKeyNames.BuildTool, "SonarLint.CSharp" },
                 { StandardTableKeyNames.ErrorCode, "csharpsquid:S222" },
-                { SonarLintTableControlConstants.IssueVizColumnName, Mock.Of<IAnalysisIssueVisualization>() }
+                { SonarLintTableControlConstants.IssueVizColumnName, Substitute.For<IAnalysisIssueVisualization>() }
             });
+        MockErrorList(cppIssueHandle, jsIssueHandle);
 
-            var errorList = CreateErrorList(cppIssueHandle, jsIssueHandle);
-            var serviceProvider = CreateServiceOperation(errorList);
+        var result = testSubject.TryGetIssueFromSelectedRow(out _);
 
-            var testSubject = new ErrorListHelper(serviceProvider);
-            var result = testSubject.TryGetIssueFromSelectedRow(out _);
-
-            result.Should().BeFalse();
-        }
+        result.Should().BeFalse();
+    }
 
-        [TestMethod]
-        public void TryGetRoslynIssueFromSelectedRow_SingleRoslynIssue_IssueReturned()
-        {
-            var path = "filepath";
-            var line = 12;
-            var column = 101;
-            var errorCode = "javascript:S333";
-            var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
+    [TestMethod]
+    public void TryGetRoslynIssueFromSelectedRow_SingleRoslynIssue_IssueReturned()
+    {
+        var path = "filepath";
+        var line = 12;
+        var column = 101;
+        var errorCode = "javascript:S333";
+        var issueHandle = CreateIssueHandle(111,
+            new Dictionary<string, object>
             {
                 { StandardTableKeyNames.BuildTool, "SonarLint" },
-                { StandardTableKeyNames.ErrorCode, errorCode},
+                { StandardTableKeyNames.ErrorCode, errorCode },
                 { StandardTableKeyNames.DocumentName, path },
                 { StandardTableKeyNames.Line, line },
                 { StandardTableKeyNames.Column, column }
             });
-            var errorList = CreateErrorList(issueHandle);
-            var serviceProvider = CreateServiceOperation(errorList);
-
-            var testSubject = new ErrorListHelper(serviceProvider);
-            bool result = testSubject.TryGetRoslynIssueFromSelectedRow(out var issue);
-
-            result.Should().BeTrue();
-            issue.RuleId.Should().BeSameAs(errorCode);
-            issue.FilePath.Should().BeSameAs(path);
-            issue.StartLine.Should().Be(line + 1);
-            issue.RoslynStartLine.Should().Be(line + 1);
-            issue.RoslynStartColumn.Should().Be(column + 1);
-            issue.LineHash.Should().BeNull();
-        }
+        MockErrorList(issueHandle);
 
-        [TestMethod]
-        public void TryGetRoslynIssueFromSelectedRow_NonSonarIssue_NothingReturned()
-        {
-            var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
+        var result = testSubject.TryGetRoslynIssueFromSelectedRow(out var issue);
+
+        result.Should().BeTrue();
+        issue.RuleId.Should().BeSameAs(errorCode);
+        issue.FilePath.Should().BeSameAs(path);
+        issue.StartLine.Should().Be(line + 1);
+        issue.RoslynStartLine.Should().Be(line + 1);
+        issue.RoslynStartColumn.Should().Be(column + 1);
+        issue.LineHash.Should().BeNull();
+    }
+
+    [TestMethod]
+    public void TryGetRoslynIssueFromSelectedRow_NonSonarIssue_NothingReturned()
+    {
+        var issueHandle = CreateIssueHandle(111,
+            new Dictionary<string, object>
             {
                 { StandardTableKeyNames.BuildTool, "Not SonarLint" },
-                { StandardTableKeyNames.ErrorCode, "javascript:S333"},
+                { StandardTableKeyNames.ErrorCode, "javascript:S333" },
                 { StandardTableKeyNames.DocumentName, "filepath" },
                 { StandardTableKeyNames.Line, 1 },
                 { StandardTableKeyNames.Column, 2 }
             });
-            var errorList = CreateErrorList(issueHandle);
-            var serviceProvider = CreateServiceOperation(errorList);
+        MockErrorList(issueHandle);
 
-            var testSubject = new ErrorListHelper(serviceProvider);
-            bool result = testSubject.TryGetRoslynIssueFromSelectedRow(out _);
+        var result = testSubject.TryGetRoslynIssueFromSelectedRow(out _);
 
-            result.Should().BeFalse();
-        }
+        result.Should().BeFalse();
+    }
 
-        [TestMethod]
-        public void TryGetRoslynIssueFromSelectedRow_NoFilePath_NothingReturned()
-        {
-            var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
+    [TestMethod]
+    public void TryGetRoslynIssueFromSelectedRow_NoFilePath_NothingReturned()
+    {
+        var issueHandle = CreateIssueHandle(111,
+            new Dictionary<string, object>
             {
                 { StandardTableKeyNames.BuildTool, "SonarLint" },
-                { StandardTableKeyNames.ErrorCode, "javascript:S333"},
-                { StandardTableKeyNames.Line, 1 },
-                { StandardTableKeyNames.Column, 2 }
+                { StandardTableKeyNames.ErrorCode, "javascript:S333" },
+                { StandardTableKeyNames.Line, 1 }, { StandardTableKeyNames.Column, 2 }
             });
-            var errorList = CreateErrorList(issueHandle);
-            var serviceProvider = CreateServiceOperation(errorList);
+        MockErrorList(issueHandle);
 
-            var testSubject = new ErrorListHelper(serviceProvider);
-            bool result = testSubject.TryGetRoslynIssueFromSelectedRow(out _);
+        var result = testSubject.TryGetRoslynIssueFromSelectedRow(out _);
 
-            result.Should().BeFalse();
-        }
+        result.Should().BeFalse();
+    }
 
-        [TestMethod]
-        public void TryGetRoslynIssueFromSelectedRow_NoStartLine_NothingReturned()
-        {
-            var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
+    [TestMethod]
+    public void TryGetRoslynIssueFromSelectedRow_NoStartLine_NothingReturned()
+    {
+        var issueHandle = CreateIssueHandle(111,
+            new Dictionary<string, object>
             {
                 { StandardTableKeyNames.BuildTool, "SonarLint" },
-                { StandardTableKeyNames.ErrorCode, "javascript:S333"},
+                { StandardTableKeyNames.ErrorCode, "javascript:S333" },
                 { StandardTableKeyNames.DocumentName, "filepath" },
-                { StandardTableKeyNames.Column, 2 },
+                { StandardTableKeyNames.Column, 2 }
             });
-            var errorList = CreateErrorList(issueHandle);
-            var serviceProvider = CreateServiceOperation(errorList);
+        MockErrorList(issueHandle);
 
-            var testSubject = new ErrorListHelper(serviceProvider);
-            bool result = testSubject.TryGetRoslynIssueFromSelectedRow(out _);
+        var result = testSubject.TryGetRoslynIssueFromSelectedRow(out _);
 
-            result.Should().BeFalse();
-        }
+        result.Should().BeFalse();
+    }
 
-        [TestMethod]
-        public void TryGetRoslynIssueFromSelectedRow_NoStartColumn_NothingReturned()
-        {
-            var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
+    [TestMethod]
+    public void TryGetRoslynIssueFromSelectedRow_NoStartColumn_NothingReturned()
+    {
+        var issueHandle = CreateIssueHandle(111,
+            new Dictionary<string, object>
             {
                 { StandardTableKeyNames.BuildTool, "SonarLint" },
-                { StandardTableKeyNames.ErrorCode, "javascript:S333"},
+                { StandardTableKeyNames.ErrorCode, "javascript:S333" },
                 { StandardTableKeyNames.DocumentName, "filepath" },
-                { StandardTableKeyNames.Line, 1 },
+                { StandardTableKeyNames.Line, 1 }
             });
-            var errorList = CreateErrorList(issueHandle);
-            var serviceProvider = CreateServiceOperation(errorList);
+        MockErrorList(issueHandle);
 
-            var testSubject = new ErrorListHelper(serviceProvider);
-            bool result = testSubject.TryGetRoslynIssueFromSelectedRow(out _);
+        var result = testSubject.TryGetRoslynIssueFromSelectedRow(out _);
 
-            result.Should().BeFalse();
-        }
-
-        [TestMethod]
-        [DataRow("S666", "csharpsquid", "S666", "SonarAnalyzer.CSharp", null)]
-        [DataRow("S666", "vbnet", "S666", "SonarAnalyzer.VisualBasic", null)]
-        [DataRow("S234", "vbnet", "S234", "SonarAnalyzer.VisualBasic", null)]
-        [DataRow("c:S111", "c", "S111", "SonarLint", null)]
-        [DataRow("cpp:S222", "cpp", "S222", "SonarLint", null)]
-        [DataRow("javascript:S333", "javascript", "S333", "SonarLint", null)]
-        [DataRow("typescript:S444", "typescript", "S444", "SonarLint", null)]
-        [DataRow("secrets:S555", "secrets", "S555", "SonarLint", null)]
-        [DataRow("foo:bar", "foo", "bar", "SonarLint", null)]
-        [DataRow("S666", "csharpsquid", "S666", null, "https://rules.sonarsource.com/csharp/RSPEC-666/")]
-        [DataRow("S666", "vbnet", "S666", null, "https://rules.sonarsource.com/vbnet/RSPEC-666/")]
-        [DataRow("S234", "vbnet", "S234", null, "https://rules.sonarsource.com/vbnet/RSPEC-234/")]
-        public void TryGetRuleIdFromSelectedRow_SingleSonarIssue_ErrorCodeReturned(string fullRuleKey, string expectedRepo, string expectedRule, string buildTool, string helpLink)
-        {
-            // Arrange
-            var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
-            {
-                { StandardTableKeyNames.BuildTool, buildTool },
-                { StandardTableKeyNames.HelpLink, helpLink },
-                { StandardTableKeyNames.ErrorCode, fullRuleKey }
-            });
-
-            var errorList = CreateErrorList(issueHandle);
-            var serviceProvider = CreateServiceOperation(errorList);
+        result.Should().BeFalse();
+    }
 
-            // Act
-            var testSubject = new ErrorListHelper(serviceProvider);
-            bool result = testSubject.TryGetRuleIdFromSelectedRow(out var ruleId);
+    [TestMethod]
+    [DataRow("S666", "csharpsquid", "S666", "SonarAnalyzer.CSharp", null)]
+    [DataRow("S666", "vbnet", "S666", "SonarAnalyzer.VisualBasic", null)]
+    [DataRow("S234", "vbnet", "S234", "SonarAnalyzer.VisualBasic", null)]
+    [DataRow("c:S111", "c", "S111", "SonarLint", null)]
+    [DataRow("cpp:S222", "cpp", "S222", "SonarLint", null)]
+    [DataRow("javascript:S333", "javascript", "S333", "SonarLint", null)]
+    [DataRow("typescript:S444", "typescript", "S444", "SonarLint", null)]
+    [DataRow("secrets:S555", "secrets", "S555", "SonarLint", null)]
+    [DataRow("foo:bar", "foo", "bar", "SonarLint", null)]
+    [DataRow("S666", "csharpsquid", "S666", null, "https://rules.sonarsource.com/csharp/RSPEC-666/")]
+    [DataRow("S666", "vbnet", "S666", null, "https://rules.sonarsource.com/vbnet/RSPEC-666/")]
+    [DataRow("S234", "vbnet", "S234", null, "https://rules.sonarsource.com/vbnet/RSPEC-234/")]
+    public void TryGetRuleIdFromSelectedRow_SingleSonarIssue_ErrorCodeReturned(
+        string fullRuleKey,
+        string expectedRepo,
+        string expectedRule,
+        string buildTool,
+        string helpLink)
+    {
+        var issueHandle = CreateIssueHandle(111,
+            new Dictionary<string, object> { { StandardTableKeyNames.BuildTool, buildTool }, { StandardTableKeyNames.HelpLink, helpLink }, { StandardTableKeyNames.ErrorCode, fullRuleKey } });
+        MockErrorList(issueHandle);
 
-            // Assert
-            result.Should().BeTrue();
-            ruleId.RepoKey.Should().Be(expectedRepo);
-            ruleId.RuleKey.Should().Be(expectedRule);
-        }
+        var result = testSubject.TryGetRuleIdFromSelectedRow(out var ruleId);
 
-        [TestMethod]
-        [DataRow("S666", "csharpsquid", "S666", "SonarAnalyzer.CSharp", null)]
-        [DataRow("S666", "vbnet", "S666", "SonarAnalyzer.VisualBasic", null)]
-        [DataRow("S234", "vbnet", "S234", "SonarAnalyzer.VisualBasic", null)]
-        [DataRow("c:S111", "c", "S111", "SonarLint", null)]
-        [DataRow("cpp:S222", "cpp", "S222", "SonarLint", null)]
-        [DataRow("javascript:S333", "javascript", "S333", "SonarLint", null)]
-        [DataRow("typescript:S444", "typescript", "S444", "SonarLint", null)]
-        [DataRow("secrets:S555", "secrets", "S555", "SonarLint", null)]
-        [DataRow("foo:bar", "foo", "bar", "SonarLint", null)]
-        [DataRow("S666", "csharpsquid", "S666", null, "https://rules.sonarsource.com/csharp/RSPEC-666/")]
-        [DataRow("S666", "vbnet", "S666", null, "https://rules.sonarsource.com/vbnet/RSPEC-666/")]
-        [DataRow("S234", "vbnet", "S234", null, "https://rules.sonarsource.com/vbnet/RSPEC-234/")]
-        public void TryGetRuleId_FromHandle_ErrorCodeReturned(string fullRuleKey, string expectedRepo, string expectedRule, string buildTool, string helpLink)
-        {
-            // Note: this is a copy of TryGetRuleIdFromSelectedRow_SingleSonarIssue_ErrorCodeReturned,
-            //       but without the serviceProvider and IErrorList setup
+        result.Should().BeTrue();
+        ruleId.RepoKey.Should().Be(expectedRepo);
+        ruleId.RuleKey.Should().Be(expectedRule);
+    }
 
-            // Arrange
-            var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
-            {
-                { StandardTableKeyNames.BuildTool, buildTool },
-                { StandardTableKeyNames.HelpLink, helpLink },
-                { StandardTableKeyNames.ErrorCode, fullRuleKey }
-            });
+    [TestMethod]
+    [DataRow("S666", "csharpsquid", "S666", "SonarAnalyzer.CSharp", null)]
+    [DataRow("S666", "vbnet", "S666", "SonarAnalyzer.VisualBasic", null)]
+    [DataRow("S234", "vbnet", "S234", "SonarAnalyzer.VisualBasic", null)]
+    [DataRow("c:S111", "c", "S111", "SonarLint", null)]
+    [DataRow("cpp:S222", "cpp", "S222", "SonarLint", null)]
+    [DataRow("javascript:S333", "javascript", "S333", "SonarLint", null)]
+    [DataRow("typescript:S444", "typescript", "S444", "SonarLint", null)]
+    [DataRow("secrets:S555", "secrets", "S555", "SonarLint", null)]
+    [DataRow("foo:bar", "foo", "bar", "SonarLint", null)]
+    [DataRow("S666", "csharpsquid", "S666", null, "https://rules.sonarsource.com/csharp/RSPEC-666/")]
+    [DataRow("S666", "vbnet", "S666", null, "https://rules.sonarsource.com/vbnet/RSPEC-666/")]
+    [DataRow("S234", "vbnet", "S234", null, "https://rules.sonarsource.com/vbnet/RSPEC-234/")]
+    public void TryGetRuleId_FromHandle_ErrorCodeReturned(
+        string fullRuleKey,
+        string expectedRepo,
+        string expectedRule,
+        string buildTool,
+        string helpLink)
+    {
+        // Note: this is a copy of TryGetRuleIdFromSelectedRow_SingleSonarIssue_ErrorCodeReturned,
+        //       but without the serviceProvider and IErrorList setup
+        var issueHandle = CreateIssueHandle(111,
+            new Dictionary<string, object> { { StandardTableKeyNames.BuildTool, buildTool }, { StandardTableKeyNames.HelpLink, helpLink }, { StandardTableKeyNames.ErrorCode, fullRuleKey } });
 
-            // Act
-            var testSubject = new ErrorListHelper(Mock.Of<IVsUIServiceOperation>());
-            bool result = testSubject.TryGetRuleId(issueHandle, out var ruleId);
+        var result = testSubject.TryGetRuleId(issueHandle, out var ruleId);
 
-            // Assert
-            result.Should().BeTrue();
-            ruleId.RepoKey.Should().Be(expectedRepo);
-            ruleId.RuleKey.Should().Be(expectedRule);
-        }
+        result.Should().BeTrue();
+        ruleId.RepoKey.Should().Be(expectedRepo);
+        ruleId.RuleKey.Should().Be(expectedRule);
+    }
 
-        [TestMethod]
-        public void TryGetRuleIdFromSelectedRow_NonStandardErrorCode_NoException_ErrorCodeNotReturned()
+    [TestMethod]
+    public void TryGetRuleIdFromSelectedRow_NonStandardErrorCode_NoException_ErrorCodeNotReturned()
+    {
+        var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
         {
-            // Arrange
-            var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
-            {
-                { StandardTableKeyNames.BuildTool, "SonarLint" },
-                { StandardTableKeyNames.ErrorCode, ":" } // should not happen
-            });
+            { StandardTableKeyNames.BuildTool, "SonarLint" },
+            { StandardTableKeyNames.ErrorCode, ":" } // should not happen
+        });
+        MockErrorList(issueHandle);
 
-            var errorList = CreateErrorList(issueHandle);
-            var serviceProvider = CreateServiceOperation(errorList);
+        var result = testSubject.TryGetRuleIdFromSelectedRow(out var errorCode);
 
-            // Act
-            var testSubject = new ErrorListHelper(serviceProvider);
-            bool result = testSubject.TryGetRuleIdFromSelectedRow(out var errorCode);
-
-            // Assert
-            result.Should().BeFalse();
-            errorCode.Should().BeNull();
-        }
+        result.Should().BeFalse();
+        errorCode.Should().BeNull();
+    }
 
-        [TestMethod]
-        public void TryGetRuleIdFromSelectedRow_MultipleItemsSelected_ErrorCodeNotReturned()
+    [TestMethod]
+    public void TryGetRuleIdFromSelectedRow_MultipleItemsSelected_ErrorCodeNotReturned()
+    {
+        var cppIssueHandle = CreateIssueHandle(111, new Dictionary<string, object>
         {
-            var cppIssueHandle = CreateIssueHandle(111, new Dictionary<string, object>
-            {
-                { StandardTableKeyNames.BuildTool, "SonarLint" },
-                { StandardTableKeyNames.ErrorCode, "cpp:S222" }
-            });
-            var jsIssueHandle = CreateIssueHandle(222, new Dictionary<string, object>
-            {
-                { StandardTableKeyNames.BuildTool, "SonarLint.CSharp" },
-                { StandardTableKeyNames.ErrorCode, "csharpsquid:S222" }
-            });
-
-            var errorList = CreateErrorList(cppIssueHandle, jsIssueHandle);
-            var serviceProvider = CreateServiceOperation(errorList);
+            { StandardTableKeyNames.BuildTool, "SonarLint" },
+            { StandardTableKeyNames.ErrorCode, "cpp:S222" }
+        });
+        var jsIssueHandle = CreateIssueHandle(222, new Dictionary<string, object>
+        {
+            { StandardTableKeyNames.BuildTool, "SonarLint.CSharp" },
+            { StandardTableKeyNames.ErrorCode, "csharpsquid:S222" }
+        });
+        MockErrorList(cppIssueHandle, jsIssueHandle);
 
-            // Act
-            var testSubject = new ErrorListHelper(serviceProvider);
-            bool result = testSubject.TryGetRuleIdFromSelectedRow(out var errorCode);
+        var result = testSubject.TryGetRuleIdFromSelectedRow(out var errorCode);
 
-            // Assert
-            result.Should().BeFalse();
-            errorCode.Should().BeNull();
-        }
+        result.Should().BeFalse();
+        errorCode.Should().BeNull();
+    }
 
-        [TestMethod]
-        public void TryGetRuleIdFromSelectedRow_NotSonarLintIssue()
+    [TestMethod]
+    public void TryGetRuleIdFromSelectedRow_NotSonarLintIssue()
+    {
+        var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
         {
-            // Arrange
-            var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
-            {
-                { StandardTableKeyNames.BuildTool, new object() },
-                { StandardTableKeyNames.ErrorCode, "cpp:S333" }
-            });
+            { StandardTableKeyNames.BuildTool, new object() },
+            { StandardTableKeyNames.ErrorCode, "cpp:S333" }
+        });
+        MockErrorList(issueHandle);
 
-            var errorList = CreateErrorList(issueHandle);
-            var serviceProvider = CreateServiceOperation(errorList);
+        var result = testSubject.TryGetRuleIdFromSelectedRow(out var errorCode);
 
-            // Act
-            var testSubject = new ErrorListHelper(serviceProvider);
-            bool result = testSubject.TryGetRuleIdFromSelectedRow(out var errorCode);
-
-            // Assert
-            result.Should().BeFalse();
-            errorCode.Should().BeNull();
-        }
+        result.Should().BeFalse();
+        errorCode.Should().BeNull();
+    }
 
-        [TestMethod]
-        [DataRow("cpp:S333", "AnotherAnalyzer", null)]
-        [DataRow("S666", "AnotherAnalyzerWithSonarHelpLink", "https://rules.sonarsource.com/csharp/RSPEC-666/")]
-        [DataRow("S234",  "SomeOtherAnalyzer", "https://rules.sonarsource.com/vbnet/RSPEC-234/")]
-        public void TryGetRuleId_FromHandle_NotSonarLintIssue(string fullRuleKey, object buildTool, string helpLink)
+    [TestMethod]
+    [DataRow("cpp:S333", "AnotherAnalyzer", null)]
+    [DataRow("S666", "AnotherAnalyzerWithSonarHelpLink", "https://rules.sonarsource.com/csharp/RSPEC-666/")]
+    [DataRow("S234", "SomeOtherAnalyzer", "https://rules.sonarsource.com/vbnet/RSPEC-234/")]
+    public void TryGetRuleId_FromHandle_NotSonarLintIssue(string fullRuleKey, object buildTool, string helpLink)
+    {
+        // Note: this is a copy of TryGetRuleIdFromSelectedRow_SingleSonarIssue_ErrorCodeReturned,
+        //       but without the serviceProvider and IErrorList setup
+        var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
         {
-            // Note: this is a copy of TryGetRuleIdFromSelectedRow_SingleSonarIssue_ErrorCodeReturned,
-            //       but without the serviceProvider and IErrorList setup
+            { StandardTableKeyNames.BuildTool, buildTool },
+            { StandardTableKeyNames.HelpLink, helpLink },
+            { StandardTableKeyNames.ErrorCode, fullRuleKey }
+        });
 
-            // Arrange
-            var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
-            {
-                { StandardTableKeyNames.BuildTool, buildTool },
-                { StandardTableKeyNames.HelpLink, helpLink },
-                { StandardTableKeyNames.ErrorCode, fullRuleKey }
-            });
-
-            // Act
-            var testSubject = new ErrorListHelper(Mock.Of<IVsUIServiceOperation>());
-            bool result = testSubject.TryGetRuleId(issueHandle, out var errorCode);
+        var result = testSubject.TryGetRuleId(issueHandle, out var errorCode);
 
-            // Assert
-            result.Should().BeFalse();
-            errorCode.Should().BeNull();
-        }
+        result.Should().BeFalse();
+        errorCode.Should().BeNull();
+    }
 
-        [TestMethod]
-        public void TryGetRuleIdAndSuppressionStateFromSelectedRow_NoSuppressionState_ReturnsIsNotSuppressed()
+    [TestMethod]
+    public void TryGetRuleIdAndSuppressionStateFromSelectedRow_NoSuppressionState_ReturnsIsNotSuppressed()
+    {
+        var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
         {
-            // Arrange
-            var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
-            {
-                { StandardTableKeyNames.BuildTool, "SonarLint" },
-                { StandardTableKeyNames.ErrorCode, "cpp:S222" }
-            });
+            { StandardTableKeyNames.BuildTool, "SonarLint" },
+            { StandardTableKeyNames.ErrorCode, "cpp:S222" }
+        });
+        MockErrorList(issueHandle);
 
-            var errorList = CreateErrorList(issueHandle);
-            var serviceProvider = CreateServiceOperation(errorList);
+        var result = testSubject.TryGetRuleIdAndSuppressionStateFromSelectedRow(out _, out var isSuppressed);
 
-            // Act
-            var testSubject = new ErrorListHelper(serviceProvider);
-            bool result = testSubject.TryGetRuleIdAndSuppressionStateFromSelectedRow(out var ruleId, out var isSuppressed);
-
-            // Assert
-            result.Should().BeTrue();
-            isSuppressed.Should().BeFalse();
-        }
+        result.Should().BeTrue();
+        isSuppressed.Should().BeFalse();
+    }
 
-        [DataTestMethod]
-        [DataRow(SuppressionState.Suppressed, true)]
-        [DataRow(SuppressionState.NotApplicable, false)]
-        [DataRow(SuppressionState.Active, false)]
-        public void TryGetRuleIdAndSuppressionStateFromSelectedRow_NoSuppressionState_ReturnsIsNotSuppressed(SuppressionState suppressionState, bool expectedSuppression)
-        {
-            // Arrange
-            var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
+    [DataTestMethod]
+    [DataRow(SuppressionState.Suppressed, true)]
+    [DataRow(SuppressionState.NotApplicable, false)]
+    [DataRow(SuppressionState.Active, false)]
+    public void TryGetRuleIdAndSuppressionStateFromSelectedRow_NoSuppressionState_ReturnsIsNotSuppressed(SuppressionState suppressionState, bool expectedSuppression)
+    {
+        var issueHandle = CreateIssueHandle(111,
+            new Dictionary<string, object>
             {
                 { StandardTableKeyNames.BuildTool, "SonarLint" },
                 { StandardTableKeyNames.ErrorCode, "cpp:S222" },
-                { StandardTableKeyNames.SuppressionState, suppressionState },
+                { StandardTableKeyNames.SuppressionState, suppressionState }
             });
+        MockErrorList(issueHandle);
 
-            var errorList = CreateErrorList(issueHandle);
-            var serviceProvider = CreateServiceOperation(errorList);
-
-            // Act
-            var testSubject = new ErrorListHelper(serviceProvider);
-            bool result = testSubject.TryGetRuleIdAndSuppressionStateFromSelectedRow(out var ruleId, out var isSuppressed);
+        var result = testSubject.TryGetRuleIdAndSuppressionStateFromSelectedRow(out _, out var isSuppressed);
 
-            // Assert
-            result.Should().BeTrue();
-            isSuppressed.Should().Be(expectedSuppression);
-        }
+        result.Should().BeTrue();
+        isSuppressed.Should().Be(expectedSuppression);
+    }
 
-        [TestMethod]
-        public void TryGetFilterableIssue_SonarIssue_IssueReturned()
-        {
-            var issueMock = Mock.Of<IAnalysisIssueVisualization>();
-            var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
+    [TestMethod]
+    public void TryGetFilterableIssue_SonarIssue_IssueReturned()
+    {
+        var issueHandle = CreateIssueHandle(111,
+            new Dictionary<string, object>
             {
                 { StandardTableKeyNames.BuildTool, "SonarLint" },
-                { StandardTableKeyNames.ErrorCode, "javascript:S333"},
+                { StandardTableKeyNames.ErrorCode, "javascript:S333" },
                 { SonarLintTableControlConstants.IssueVizColumnName, issueMock }
             });
-            var errorList = CreateErrorList(issueHandle);
-            var serviceProvider = CreateServiceOperation(errorList);
-            var testSubject = new ErrorListHelper(serviceProvider);
+        MockErrorList(issueHandle);
 
-            bool result = testSubject.TryGetFilterableIssue(issueHandle, out var issue);
+        var result = testSubject.TryGetFilterableIssue(issueHandle, out var issue);
 
-            result.Should().BeTrue();
-            issue.Should().BeSameAs(issueMock);
-        }
+        result.Should().BeTrue();
+        issue.Should().BeSameAs(issueMock);
+    }
 
-        [TestMethod]
-        public void TryGetFilterableIssue_NoAnalysisIssue_IssueNotReturned()
-        {
-            var issueHandle = CreateIssueHandle(111, new Dictionary<string, object>
+    [TestMethod]
+    public void TryGetFilterableIssue_NoAnalysisIssue_IssueNotReturned()
+    {
+        var issueHandle = CreateIssueHandle(111,
+            new Dictionary<string, object>
             {
                 { StandardTableKeyNames.BuildTool, "SonarLint" },
-                { StandardTableKeyNames.ErrorCode, "javascript:S333"},
+                { StandardTableKeyNames.ErrorCode, "javascript:S333" },
                 { SonarLintTableControlConstants.IssueVizColumnName, null }
             });
-            var errorList = CreateErrorList(issueHandle);
-            var serviceProvider = CreateServiceOperation(errorList);
-
-            var testSubject = new ErrorListHelper(serviceProvider);
-            var result = testSubject.TryGetFilterableIssue(issueHandle,out _);
+        MockErrorList(issueHandle);
 
-            result.Should().BeFalse();
-        }
+        var result = testSubject.TryGetFilterableIssue(issueHandle, out _);
 
-        private IVsUIServiceOperation CreateServiceOperation(IErrorList svcToPassToCallback)
-        {
-            var serviceOp = new Mock<IVsUIServiceOperation>();
+        result.Should().BeFalse();
+    }
 
-            // Set up the mock to invoke the operation with the supplied VS service
-            serviceOp.Setup(x => x.Execute<SVsErrorList, IErrorList, bool>(It.IsAny<Func<IErrorList, bool>>()))
-                .Returns<Func<IErrorList, bool>>(op => op(svcToPassToCallback));
+    private void MockErrorList(params ITableEntryHandle[] entries)
+    {
+        var errorList = CreateErrorList(entries);
+        // Set up the mock to invoke the operation with the supplied VS service
+        vsUiServiceOperation.Execute<SVsErrorList, IErrorList, bool>(Arg.Any<Func<IErrorList, bool>>())
+            .Returns(callInfo =>
+            {
+                var func = callInfo.Arg<Func<IErrorList, bool>>();
+                return func(errorList);
+            });
+    }
 
-            return serviceOp.Object;
-        }
+    private static IErrorList CreateErrorList(params ITableEntryHandle[] entries)
+    {
+        var mockWpfTable = Substitute.For<IWpfTableControl>();
+        mockWpfTable.SelectedEntries.Returns(entries);
 
-        private static IErrorList CreateErrorList(params ITableEntryHandle[] entries)
-        {
-            var mockWpfTable = new Mock<IWpfTableControl>();
-            mockWpfTable.Setup(x => x.SelectedEntries).Returns(entries);
+        var mockErrorList = Substitute.For<IErrorList>();
+        mockErrorList.TableControl.Returns(mockWpfTable);
+        return mockErrorList;
+    }
 
-            var mockErrorList = new Mock<IErrorList>();
-            mockErrorList.Setup(x => x.TableControl).Returns(mockWpfTable.Object);
-            return mockErrorList.Object;
-        }
+    private static ITableEntryHandle CreateIssueHandle(int index, IDictionary<string, object> issueProperties)
+    {
+        // Snapshots would normally have multiple versions; each version would have a unique
+        // index, with a corresponding handle.
+        // Here, just create a dummy snapshot with a single version using the specified index
+        var issueSnapshot = (ITableEntriesSnapshot)new DummySnapshot { Index = index, Properties = issueProperties };
 
-        private static ITableEntryHandle CreateIssueHandle(int index, IDictionary<string, object> issueProperties)
+        var mockHandle = Substitute.For<ITableEntryHandle>();
+        mockHandle.TryGetSnapshot(out _, out _).Returns(callInfo =>
         {
-            // Snapshots would normally have multiple versions; each version would have a unique
-            // index, with a corresponding handle.
-            // Here, just create a dummy snapshot with a single version using the specified index
-            var issueSnapshot = (ITableEntriesSnapshot)new DummySnapshot
-            {
-                Index = index,
-                Properties = issueProperties
-            };
-
-            var mockHandle = new Mock<ITableEntryHandle>();
-            mockHandle.Setup(x => x.TryGetSnapshot(out issueSnapshot, out index)).Returns(true);
-            return mockHandle.Object;
-        }
+            callInfo[0] = issueSnapshot;
+            callInfo[1] = index;
+            return true;
+        });
+        return mockHandle;
+    }
 
-        #region Helper classes
+    #region Helper classes
 
-        private sealed class DummySnapshot : ITableEntriesSnapshot
-        {
-            public int Index { get; set; }
-            public IDictionary<string, object> Properties { get; set; }
+    private sealed class DummySnapshot : ITableEntriesSnapshot
+    {
+        public int Index { get; set; }
+        public IDictionary<string, object> Properties { get; set; }
 
-            #region ITableEntriesSnapshot methods
+        #region ITableEntriesSnapshot methods
 
-            public int Count => throw new NotImplementedException();
-            public int VersionNumber => throw new NotImplementedException();
+        public int Count => throw new NotImplementedException();
+        public int VersionNumber => throw new NotImplementedException();
 
-            public void Dispose() => throw new NotImplementedException();
+        public void Dispose() => throw new NotImplementedException();
 
-            public int IndexOf(int currentIndex, ITableEntriesSnapshot newSnapshot) => throw new NotImplementedException();
+        public int IndexOf(int currentIndex, ITableEntriesSnapshot newSnapshot) => throw new NotImplementedException();
 
-            public void StartCaching() => throw new NotImplementedException();
+        public void StartCaching() => throw new NotImplementedException();
 
-            public void StopCaching() => throw new NotImplementedException();
+        public void StopCaching() => throw new NotImplementedException();
 
-            public bool TryGetValue(int index, string keyName, out object content)
+        public bool TryGetValue(int index, string keyName, out object content)
+        {
+            if (index == Index)
             {
-                if (index == Index)
-                {
-                    return Properties.TryGetValue(keyName, out content);
-                }
-                content = null;
-                return false;
+                return Properties.TryGetValue(keyName, out content);
             }
-
-            #endregion ITableEntriesSnapshot methods
+            content = null;
+            return false;
         }
 
-        #endregion Helper classes
+        #endregion ITableEntriesSnapshot methods
     }
+
+    #endregion Helper classes
 }
diff --git a/src/Infrastructure.VS/ErrorListHelper.cs b/src/Infrastructure.VS/ErrorListHelper.cs
index 4beb5f0b80..f31c474f92 100644
--- a/src/Infrastructure.VS/ErrorListHelper.cs
+++ b/src/Infrastructure.VS/ErrorListHelper.cs
@@ -26,209 +26,199 @@
 using SonarLint.VisualStudio.Core;
 using SonarLint.VisualStudio.Core.Suppressions;
 
-namespace SonarLint.VisualStudio.Infrastructure.VS
+namespace SonarLint.VisualStudio.Infrastructure.VS;
+
+[Export(typeof(IErrorListHelper))]
+[PartCreationPolicy(CreationPolicy.Shared)]
+[method: ImportingConstructor]
+public class ErrorListHelper(IVsUIServiceOperation vSServiceOperation) : IErrorListHelper
 {
-    [Export(typeof(IErrorListHelper))]
-    [PartCreationPolicy(CreationPolicy.Shared)]
-    public class ErrorListHelper : IErrorListHelper
+    public bool TryGetRuleIdFromSelectedRow(out SonarCompositeRuleId ruleId)
     {
-        private readonly IVsUIServiceOperation vSServiceOperation;
+        SonarCompositeRuleId ruleIdOut = null;
+        var result = vSServiceOperation.Execute<SVsErrorList, IErrorList, bool>(errorList =>
+            TryGetSelectedTableEntry(errorList, out var handle) && TryGetRuleId(handle, out ruleIdOut));
 
-        [ImportingConstructor]
-        public ErrorListHelper(IVsUIServiceOperation vSServiceOperation)
-        {
-            this.vSServiceOperation = vSServiceOperation;
-        }
-
-        public bool TryGetRuleIdFromSelectedRow(out SonarCompositeRuleId ruleId)
-        {
-            SonarCompositeRuleId ruleIdOut = null;
-            var result = vSServiceOperation.Execute<SVsErrorList, IErrorList, bool>(errorList =>
-                TryGetSelectedTableEntry(errorList, out var handle) && TryGetRuleId(handle, out ruleIdOut));
+        ruleId = ruleIdOut;
 
-            ruleId = ruleIdOut;
+        return result;
+    }
 
-            return result;
+    public bool TryGetRuleId(ITableEntryHandle handle, out SonarCompositeRuleId ruleId)
+    {
+        ruleId = null;
+        if (!handle.TryGetSnapshot(out var snapshot, out var index))
+        {
+            return false;
         }
 
-        public bool TryGetRuleId(ITableEntryHandle handle, out SonarCompositeRuleId ruleId)
+        var errorCode = FindErrorCodeForEntry(snapshot, index);
+        return SonarCompositeRuleId.TryParse(errorCode, out ruleId);
+    }
+
+    public bool TryGetRuleIdAndSuppressionStateFromSelectedRow(out SonarCompositeRuleId ruleId, out bool isSuppressed)
+    {
+        SonarCompositeRuleId ruleIdOut = null;
+        var isSuppressedOut = false;
+        var result = vSServiceOperation.Execute<SVsErrorList, IErrorList, bool>(errorList =>
         {
-            ruleId = null;
-            if (!handle.TryGetSnapshot(out var snapshot, out var index))
+            if (!TryGetSelectedTableEntry(errorList, out var handle) || !TryGetRuleId(handle, out ruleIdOut))
             {
                 return false;
             }
 
-            var errorCode = FindErrorCodeForEntry(snapshot, index);
-            return SonarCompositeRuleId.TryParse(errorCode, out ruleId);
-        }
+            isSuppressedOut = IsSuppressed(handle);
+            return true;
+        });
 
-        public bool TryGetRuleIdAndSuppressionStateFromSelectedRow(out SonarCompositeRuleId ruleId, out bool isSuppressed)
-        {
-            SonarCompositeRuleId ruleIdOut = null;
-            var isSuppressedOut = false;
-            var result = vSServiceOperation.Execute<SVsErrorList, IErrorList, bool>(errorList =>
-            {
-                if (!TryGetSelectedTableEntry(errorList, out var handle) || !TryGetRuleId(handle, out ruleIdOut))
-                {
-                    return false;
-                }
+        ruleId = ruleIdOut;
+        isSuppressed = isSuppressedOut;
 
-                isSuppressedOut = IsSuppressed(handle);
-                return true;
-            });
+        return result;
+    }
 
-            ruleId = ruleIdOut;
-            isSuppressed = isSuppressedOut;
+    public bool TryGetIssueFromSelectedRow(out IFilterableIssue issue)
+    {
+        IFilterableIssue issueOut = null;
+        var result = vSServiceOperation.Execute<SVsErrorList, IErrorList, bool>(
+            errorList => TryGetSelectedTableEntry(errorList, out var handle) && TryGetFilterableIssue(handle, out issueOut));
 
-            return result;
-        }
+        issue = issueOut;
 
-        public bool TryGetIssueFromSelectedRow(out IFilterableIssue issue)
-        {
-            IFilterableIssue issueOut = null;
-            var result = vSServiceOperation.Execute<SVsErrorList, IErrorList, bool>(
-                errorList => TryGetSelectedTableEntry(errorList, out var handle) && TryGetFilterableIssue(handle, out issueOut));
+        return result;
+    }
+
+    public bool TryGetFilterableIssue(ITableEntryHandle handle, out IFilterableIssue issue)
+    {
+        IFilterableIssue issueOut = null;
+        var result = vSServiceOperation.Execute<SVsErrorList, IErrorList, bool>(
+            _ => handle.TryGetSnapshot(out var snapshot, out var index)
+                 && TryGetValue(snapshot, index, SonarLintTableControlConstants.IssueVizColumnName, out issueOut));
 
-            issue = issueOut;
+        issue = issueOut;
 
-            return result;
-        }
+        return result;
+    }
+
+    public bool TryGetRoslynIssueFromSelectedRow(out IFilterableRoslynIssue filterableRoslynIssue)
+    {
+        IFilterableRoslynIssue outIssue = null;
 
-        public bool TryGetFilterableIssue(ITableEntryHandle handle, out IFilterableIssue issue)
+        var result = vSServiceOperation.Execute<SVsErrorList, IErrorList, bool>(errorList =>
         {
-            IFilterableIssue issueOut = null;
-            var result = vSServiceOperation.Execute<SVsErrorList, IErrorList, bool>(
-                _ => handle.TryGetSnapshot(out var snapshot, out var index)
-                     && TryGetValue(snapshot, index, SonarLintTableControlConstants.IssueVizColumnName, out issueOut));
+            string errorCode;
+            if (TryGetSelectedSnapshotAndIndex(errorList, out var snapshot, out var index)
+                && (errorCode = FindErrorCodeForEntry(snapshot, index)) != null
+                && TryGetValue(snapshot, index, StandardTableKeyNames.DocumentName, out string filePath)
+                && TryGetValue(snapshot, index, StandardTableKeyNames.Line, out int line)
+                && TryGetValue(snapshot, index, StandardTableKeyNames.Column, out int column))
+            {
+                outIssue = new FilterableRoslynIssue(errorCode, filePath, line + 1, column + 1 /* error list issues are 0-based and we use 1-based line & column numbers */);
+            }
 
-            issue = issueOut;
+            return outIssue != null;
+        });
 
-            return result;
-        }
+        filterableRoslynIssue = outIssue;
 
-        public bool TryGetRoslynIssueFromSelectedRow(out IFilterableRoslynIssue filterableRoslynIssue)
-        {
-            IFilterableRoslynIssue outIssue = null;
+        return result;
+    }
 
-            var result = vSServiceOperation.Execute<SVsErrorList, IErrorList, bool>(errorList =>
-            {
-                string errorCode;
-                if (TryGetSelectedSnapshotAndIndex(errorList, out var snapshot, out var index)
-                    && (errorCode = FindErrorCodeForEntry(snapshot, index)) != null
-                    && TryGetValue(snapshot, index, StandardTableKeyNames.DocumentName, out string filePath)
-                    && TryGetValue(snapshot, index, StandardTableKeyNames.Line, out int line)
-                    && TryGetValue(snapshot, index, StandardTableKeyNames.Column, out int column))
-                {
-                    outIssue = new FilterableRoslynIssue(errorCode, filePath, line + 1, column + 1 /* error list issues are 0-based and we use 1-based line & column numbers */);
-                }
-
-                return outIssue != null;
-            });
-
-            filterableRoslynIssue = outIssue;
-
-            return result;
-        }
+    private static bool IsSuppressed(ITableEntryHandle handle) =>
+        handle.TryGetSnapshot(out var snapshot, out var index)
+        && TryGetValue(snapshot, index, StandardTableKeyNames.SuppressionState, out SuppressionState suppressionState)
+        && suppressionState == SuppressionState.Suppressed;
 
-        private static bool IsSuppressed(ITableEntryHandle handle)
+    private static string FindErrorCodeForEntry(ITableEntriesSnapshot snapshot, int index)
+    {
+        if (!TryGetValue(snapshot, index, StandardTableKeyNames.ErrorCode, out string errorCode))
         {
-            return handle.TryGetSnapshot(out var snapshot, out var index)
-                   && TryGetValue(snapshot, index, StandardTableKeyNames.SuppressionState, out SuppressionState suppressionState)
-                   && suppressionState == SuppressionState.Suppressed;
+            return null;
         }
 
-        private static string FindErrorCodeForEntry(ITableEntriesSnapshot snapshot, int index)
+        if (TryGetValue(snapshot, index, StandardTableKeyNames.BuildTool, out string buildTool))
         {
-            if (!TryGetValue(snapshot, index, StandardTableKeyNames.ErrorCode, out string errorCode))
+            // For CSharp and VisualBasic the buildTool returns the name of the analyzer package.
+            // The prefix is required for roslyn languages as the error code is in style "S111" meaning
+            // unlike other languages it has no repository prefix.
+            return buildTool switch
             {
-                return null;
-            }
+                "SonarAnalyzer.CSharp" => $"{SonarRuleRepoKeys.CSharpRules}:{errorCode}",
+                "SonarAnalyzer.VisualBasic" => $"{SonarRuleRepoKeys.VBNetRules}:{errorCode}",
+                "SonarLint" => errorCode,
+                _ => null
+            };
+        }
 
-            if (TryGetValue(snapshot, index, StandardTableKeyNames.BuildTool, out string buildTool))
+        if (TryGetValue(snapshot, index, StandardTableKeyNames.HelpLink, out string helpLink))
+        {
+            if (helpLink.Contains("rules.sonarsource.com/csharp/"))
             {
-                // For CSharp and VisualBasic the buildTool returns the name of the analyzer package.
-                // The prefix is required for roslyn languages as the error code is in style "S111" meaning
-                // unlike other languages it has no repository prefix.
-                return buildTool switch
-                {
-                    "SonarAnalyzer.CSharp" => $"{SonarRuleRepoKeys.CSharpRules}:{errorCode}",
-                    "SonarAnalyzer.VisualBasic" => $"{SonarRuleRepoKeys.VBNetRules}:{errorCode}",
-                    "SonarLint" => errorCode,
-                    _ => null
-                };
+                return $"{SonarRuleRepoKeys.CSharpRules}:{errorCode}";
             }
 
-            if (TryGetValue(snapshot, index, StandardTableKeyNames.HelpLink, out string helpLink))
+            if (helpLink.Contains("rules.sonarsource.com/vbnet/"))
             {
-                if (helpLink.Contains("rules.sonarsource.com/csharp/"))
-                {
-                    return $"{SonarRuleRepoKeys.CSharpRules}:{errorCode}";
-                }
-
-                if (helpLink.Contains("rules.sonarsource.com/vbnet/"))
-                {
-                    return $"{SonarRuleRepoKeys.VBNetRules}:{errorCode}";
-                }
+                return $"{SonarRuleRepoKeys.VBNetRules}:{errorCode}";
             }
-
-            return null;
         }
 
-        private static bool TryGetSelectedSnapshotAndIndex(IErrorList errorList, out ITableEntriesSnapshot snapshot, out int index)
-        {
-            snapshot = default;
-            index = default;
+        return null;
+    }
 
-            return TryGetSelectedTableEntry(errorList, out var handle) && handle.TryGetSnapshot(out snapshot, out index);
-        }
+    private static bool TryGetSelectedSnapshotAndIndex(IErrorList errorList, out ITableEntriesSnapshot snapshot, out int index)
+    {
+        snapshot = default;
+        index = default;
 
-        private static bool TryGetSelectedTableEntry(IErrorList errorList, out ITableEntryHandle handle)
-        {
-            handle = null;
+        return TryGetSelectedTableEntry(errorList, out var handle) && handle.TryGetSnapshot(out snapshot, out index);
+    }
 
-            var selectedItems = errorList?.TableControl?.SelectedEntries;
+    private static bool TryGetSelectedTableEntry(IErrorList errorList, out ITableEntryHandle handle)
+    {
+        handle = null;
 
-            if (selectedItems == null)
-            {
-                return false;
-            }
+        var selectedItems = errorList?.TableControl?.SelectedEntries;
 
-            foreach (var tableEntryHandle in selectedItems)
-            {
-                if (handle != null)
-                {
-                    return false; // more than one selected is not supported
-                }
+        if (selectedItems == null)
+        {
+            return false;
+        }
 
-                handle = tableEntryHandle;
+        foreach (var tableEntryHandle in selectedItems)
+        {
+            if (handle != null)
+            {
+                return false; // more than one selected is not supported
             }
 
-            return true;
+            handle = tableEntryHandle;
         }
 
-        private static bool TryGetValue<T>(
-            ITableEntriesSnapshot snapshot,
-            int index,
-            string columnName,
-            out T value)
-        {
-            value = default;
+        return true;
+    }
 
-            try
-            {
-                if (!snapshot.TryGetValue(index, columnName, out var objValue) || objValue == null)
-                {
-                    return false;
-                }
+    private static bool TryGetValue<T>(
+        ITableEntriesSnapshot snapshot,
+        int index,
+        string columnName,
+        out T value)
+    {
+        value = default;
 
-                value = (T)objValue;
-                return true;
-            }
-            catch (InvalidCastException)
+        try
+        {
+            if (!snapshot.TryGetValue(index, columnName, out var objValue) || objValue == null)
             {
                 return false;
             }
+
+            value = (T)objValue;
+            return true;
+        }
+        catch (InvalidCastException)
+        {
+            return false;
         }
     }
 }

From 946fc14ef69717531886f440f7b329b9fbd6b3c5 Mon Sep 17 00:00:00 2001
From: Gabriela Trutan <gabriela.trutan@sonarsource.com>
Date: Thu, 14 Nov 2024 17:26:04 +0100
Subject: [PATCH 5/6] SLVS-1625 CaYC AnalysisIssueVisualization and its test
 class

---
 .../Models/AnalysisIssueVisualizationTests.cs | 342 ++++++++----------
 .../Models/AnalysisIssueVisualization.cs      | 178 +++++----
 2 files changed, 228 insertions(+), 292 deletions(-)

diff --git a/src/IssueViz.UnitTests/Models/AnalysisIssueVisualizationTests.cs b/src/IssueViz.UnitTests/Models/AnalysisIssueVisualizationTests.cs
index 93dab60f90..de2516e8dd 100644
--- a/src/IssueViz.UnitTests/Models/AnalysisIssueVisualizationTests.cs
+++ b/src/IssueViz.UnitTests/Models/AnalysisIssueVisualizationTests.cs
@@ -18,248 +18,192 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-using System;
 using System.ComponentModel;
-using FluentAssertions;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Microsoft.VisualStudio.Text;
-using Moq;
 using SonarLint.VisualStudio.Core.Analysis;
 using SonarLint.VisualStudio.Core.Suppressions;
 using SonarLint.VisualStudio.IssueVisualization.Models;
 
-namespace SonarLint.VisualStudio.IssueVisualization.UnitTests.Models
+namespace SonarLint.VisualStudio.IssueVisualization.UnitTests.Models;
+
+[TestClass]
+public class AnalysisIssueVisualizationTests
 {
-    [TestClass]
-    public class AnalysisIssueVisualizationTests
-    {
-        [TestMethod]
-        public void Ctor_StepNumberIsZero()
-        {
-            var testSubject = CreateTestSubject();
-
-            testSubject.StepNumber.Should().Be(0);
-        }
-
-        [TestMethod]
-        public void Ctor_InitialFilePathIsTakenFromIssue()
-        {
-            var testSubject = CreateTestSubject(filePath: "test path");
-
-            testSubject.CurrentFilePath.Should().Be("test path");
-        }
-
-        [TestMethod]
-        public void Ctor_NullSpan_InitialSpanIsSetToGivenValue()
-        {
-            var testSubject = CreateTestSubject(span: null);
-
-            testSubject.Span.Should().BeNull();
-        }
-
-        [TestMethod]
-        public void Ctor_EmptySpan_InitialSpanIsSetToGivenValue()
-        {
-            var span = new SnapshotSpan();
-            var testSubject = CreateTestSubject(span: span);
-
-            testSubject.Span.Should().Be(span);
-        }
-
-        [TestMethod]
-        public void Ctor_NonEmptySpan_InitialSpanIsSetToGivenValue()
-        {
-            var span = CreateSpan();
-            var testSubject = CreateTestSubject(span: span);
-
-            testSubject.Span.Should().Be(span);
-        }
-
-        [TestMethod]
-        public void Location_ReturnsUnderlyingIssueLocation()
-        {
-            var testSubject = CreateTestSubject();
-            testSubject.Location.Should().Be(testSubject.Issue.PrimaryLocation);
-        }
-
-        [TestMethod]
-        public void SetCurrentFilePath_FilePathIsNull_SpanIsInvalidated()
-        {
-            // Arrange
-            var oldFilePath = "oldpath.txt";
-            var oldSpan = CreateSpan();
-
-            var testSubject = CreateTestSubject(oldFilePath, oldSpan);
-            testSubject.Span.Should().Be(oldSpan);
-            testSubject.CurrentFilePath.Should().Be(oldFilePath);
-
-            var propertyChangedEventHandler = new Mock<PropertyChangedEventHandler>();
-            testSubject.PropertyChanged += propertyChangedEventHandler.Object;
+    private readonly SnapshotSpan emptySpan = new();
+    private readonly string filePath = "filePath.txt";
 
-            // Act
-            testSubject.CurrentFilePath = null;
+    private IAnalysisIssue issue = Substitute.For<IAnalysisIssue>();
+    private AnalysisIssueVisualization issueVisualizationWithEmptySpan;
+    private AnalysisIssueVisualization issueVisualizationWithNoSpan;
+    private AnalysisIssueVisualization issueVisualizationWithNotEmptySpan;
+    private SnapshotSpan notEmptySpan;
 
-            // Assert
-            testSubject.Span.Value.IsEmpty.Should().BeTrue();
-            testSubject.CurrentFilePath.Should().BeNull();
+    [TestInitialize]
+    public void TestInitialize()
+    {
+        notEmptySpan = CreateSpan();
+        issue = Substitute.For<IAnalysisIssue>();
+        MockAnalysisIssue();
 
-            VerifyPropertyChangedRaised(propertyChangedEventHandler, nameof(testSubject.CurrentFilePath));
-            VerifyPropertyChangedRaised(propertyChangedEventHandler, nameof(testSubject.Span));
-            propertyChangedEventHandler.VerifyNoOtherCalls();
-        }
+        issueVisualizationWithNoSpan = new AnalysisIssueVisualization(null, issue, null, null);
+        issueVisualizationWithEmptySpan = new AnalysisIssueVisualization(null, issue, emptySpan, null);
+        issueVisualizationWithNotEmptySpan = new AnalysisIssueVisualization(null, issue, notEmptySpan, null);
+    }
 
-        [TestMethod]
-        public void SetCurrentFilePath_FilePathIsNotNull_SpanNotChanged()
-        {
-            // Arrange
-            var oldFilePath = "oldpath.txt";
-            var oldSpan = CreateSpan();
+    [TestMethod]
+    public void Ctor_StepNumberIsZero() => issueVisualizationWithNoSpan.StepNumber.Should().Be(0);
 
-            var testSubject = CreateTestSubject(oldFilePath, oldSpan);
-            testSubject.Span.Should().Be(oldSpan);
-            testSubject.CurrentFilePath.Should().Be(oldFilePath);
+    [TestMethod]
+    public void Ctor_InitialFilePathIsTakenFromIssue() => issueVisualizationWithNoSpan.CurrentFilePath.Should().Be(filePath);
 
-            var propertyChangedEventHandler = new Mock<PropertyChangedEventHandler>();
-            testSubject.PropertyChanged += propertyChangedEventHandler.Object;
+    [TestMethod]
+    public void Ctor_NullSpan_InitialSpanIsSetToGivenValue() => issueVisualizationWithNoSpan.Span.Should().BeNull();
 
-            // Act
-            testSubject.CurrentFilePath = "newpath.txt";
+    [TestMethod]
+    public void Ctor_EmptySpan_InitialSpanIsSetToGivenValue() => issueVisualizationWithEmptySpan.Span.Should().Be(emptySpan);
 
-            // Assert
-            testSubject.Span.Should().Be(oldSpan);
-            testSubject.CurrentFilePath.Should().Be("newpath.txt");
+    [TestMethod]
+    public void Ctor_NonEmptySpan_InitialSpanIsSetToGivenValue() => issueVisualizationWithNotEmptySpan.Span.Should().Be(notEmptySpan);
 
-            VerifyPropertyChangedRaised(propertyChangedEventHandler, nameof(testSubject.CurrentFilePath));
-            VerifyPropertyChangedNotRaised(propertyChangedEventHandler, nameof(testSubject.Span));
-            propertyChangedEventHandler.VerifyNoOtherCalls();
-        }
+    [TestMethod]
+    public void Location_ReturnsUnderlyingIssueLocation() => issueVisualizationWithEmptySpan.Location.Should().Be(issueVisualizationWithEmptySpan.Issue.PrimaryLocation);
 
-        [TestMethod]
-        public void SetCurrentFilePath_NoSubscribers_NoException()
-        {
-            var testSubject = CreateTestSubject();
+    [TestMethod]
+    public void SetCurrentFilePath_FilePathIsNull_SpanIsInvalidated()
+    {
+        VerifySpanAndLocationCorrect(filePath);
+        var propertyChangedEventHandler = MockSubscriberToPropertyChanged();
 
-            Action act = () => testSubject.CurrentFilePath = "new path";
-            act.Should().NotThrow();
+        issueVisualizationWithNotEmptySpan.CurrentFilePath = null;
 
-            testSubject.CurrentFilePath.Should().Be("new path");
-        }
+        issueVisualizationWithNotEmptySpan.Span.Value.IsEmpty.Should().BeTrue();
+        issueVisualizationWithNotEmptySpan.CurrentFilePath.Should().BeNull();
+        VerifyPropertyChangedRaised(propertyChangedEventHandler, issueVisualizationWithNotEmptySpan, nameof(issueVisualizationWithNotEmptySpan.CurrentFilePath));
+        VerifyPropertyChangedRaised(propertyChangedEventHandler, issueVisualizationWithNotEmptySpan, nameof(issueVisualizationWithNotEmptySpan.Span));
+        propertyChangedEventHandler.ReceivedCalls().Count().Should().Be(2);
+    }
 
-        [TestMethod]
-        public void SetCurrentFilePath_HasSubscribers_NotifiesSubscribers()
-        {
-            var propertyChangedEventHandler = new Mock<PropertyChangedEventHandler>();
+    [TestMethod]
+    public void SetCurrentFilePath_FilePathIsNotNull_SpanNotChanged()
+    {
+        VerifySpanAndLocationCorrect(filePath);
+        var propertyChangedEventHandler = MockSubscriberToPropertyChanged();
 
-            var testSubject = CreateTestSubject();
-            testSubject.PropertyChanged += propertyChangedEventHandler.Object;
+        issueVisualizationWithNotEmptySpan.CurrentFilePath = "newpath.txt";
 
-            testSubject.CurrentFilePath = "new path";
+        issueVisualizationWithNotEmptySpan.Span.Should().Be(notEmptySpan);
+        issueVisualizationWithNotEmptySpan.CurrentFilePath.Should().Be("newpath.txt");
+        VerifyPropertyChangedRaised(propertyChangedEventHandler, issueVisualizationWithNotEmptySpan, nameof(issueVisualizationWithNotEmptySpan.CurrentFilePath));
+        VerifyPropertyChangedNotRaised(propertyChangedEventHandler, nameof(issueVisualizationWithNotEmptySpan.Span));
+        propertyChangedEventHandler.ReceivedCalls().Count().Should().Be(1);
+    }
 
-            VerifyPropertyChangedRaised(propertyChangedEventHandler, nameof(testSubject.CurrentFilePath));
-            propertyChangedEventHandler.VerifyNoOtherCalls();
-
-            testSubject.CurrentFilePath.Should().Be("new path");
-        }
-
-        [TestMethod]
-        public void SetSpan_NoSubscribers_NoException()
-        {
-            var newSpan = CreateSpan();
-            var testSubject = CreateTestSubject();
-
-            Action act = () => testSubject.Span = newSpan;
-            act.Should().NotThrow();
-
-            testSubject.Span.Should().Be(newSpan);
-        }
-
-        [TestMethod]
-        public void SetSpan_HasSubscribers_NotifiesSubscribers()
-        {
-            var newSpan = CreateSpan();
-            var propertyChangedEventHandler = new Mock<PropertyChangedEventHandler>();
+    [TestMethod]
+    public void SetCurrentFilePath_NoSubscribers_NoException()
+    {
+        Action act = () => issueVisualizationWithNotEmptySpan.CurrentFilePath = "new path";
+        act.Should().NotThrow();
 
-            var testSubject = CreateTestSubject();
-            testSubject.PropertyChanged += propertyChangedEventHandler.Object;
+        issueVisualizationWithNotEmptySpan.CurrentFilePath.Should().Be("new path");
+    }
 
-            testSubject.Span = newSpan;
+    [TestMethod]
+    public void SetCurrentFilePath_HasSubscribers_NotifiesSubscribers()
+    {
+        var propertyChangedEventHandler = MockSubscriberToPropertyChanged();
 
-            VerifyPropertyChangedRaised(propertyChangedEventHandler, nameof(testSubject.Span));
-            propertyChangedEventHandler.VerifyNoOtherCalls();
+        issueVisualizationWithNotEmptySpan.CurrentFilePath = "new path";
 
-            testSubject.Span.Should().Be(newSpan);
-        }
+        VerifyPropertyChangedRaised(propertyChangedEventHandler, issueVisualizationWithNotEmptySpan, nameof(issueVisualizationWithNotEmptySpan.CurrentFilePath));
+        propertyChangedEventHandler.ReceivedCalls().Count().Should().Be(1);
+        issueVisualizationWithNotEmptySpan.CurrentFilePath.Should().Be("new path");
+    }
 
-        [TestMethod]
-        public void SetIsSuppressed_HasSubscribers_VerifyRaised()
-        {
-            var testSubject = CreateTestSubject();
+    [TestMethod]
+    public void SetSpan_NoSubscribers_NoException()
+    {
+        var newSpan = CreateSpan();
 
-            testSubject.IsSuppressed.Should().BeFalse();
+        Action act = () => issueVisualizationWithNotEmptySpan.Span = newSpan;
+        act.Should().NotThrow();
 
-            var propertyChangedEventHandler = new Mock<PropertyChangedEventHandler>();
-            testSubject.PropertyChanged += propertyChangedEventHandler.Object;
+        issueVisualizationWithNotEmptySpan.Span.Should().Be(newSpan);
+    }
 
-            testSubject.IsSuppressed = true;
+    [TestMethod]
+    public void SetSpan_HasSubscribers_NotifiesSubscribers()
+    {
+        var newSpan = CreateSpan();
+        var propertyChangedEventHandler = MockSubscriberToPropertyChanged();
 
-            VerifyPropertyChangedRaised(propertyChangedEventHandler, nameof(testSubject.IsSuppressed));
-            propertyChangedEventHandler.VerifyNoOtherCalls();
+        issueVisualizationWithNotEmptySpan.Span = newSpan;
 
-            testSubject.IsSuppressed.Should().BeTrue();
-        }
+        VerifyPropertyChangedRaised(propertyChangedEventHandler, issueVisualizationWithNotEmptySpan, nameof(issueVisualizationWithNotEmptySpan.Span));
+        propertyChangedEventHandler.ReceivedCalls().Count().Should().Be(1);
+        issueVisualizationWithNotEmptySpan.Span.Should().Be(newSpan);
+    }
 
-        [TestMethod]
-        public void IsFilterable()
-        {
-            var id = Guid.NewGuid();
-            var issueMock = new Mock<IAnalysisIssue>();
-            issueMock.SetupGet(x => x.Id).Returns(id);
-            issueMock.SetupGet(x => x.RuleKey).Returns("my key");
-            issueMock.SetupGet(x => x.PrimaryLocation.FilePath).Returns("x:\\aaa.foo");
-            issueMock.SetupGet(x => x.PrimaryLocation.TextRange.StartLine).Returns(999);
-            issueMock.SetupGet(x => x.PrimaryLocation.TextRange.LineHash).Returns("hash");
+    [TestMethod]
+    public void SetIsSuppressed_HasSubscribers_VerifyRaised()
+    {
+        issueVisualizationWithNotEmptySpan.IsSuppressed.Should().BeFalse();
+        var propertyChangedEventHandler = MockSubscriberToPropertyChanged();
 
-            var testSubject = new AnalysisIssueVisualization(null, issueMock.Object, new SnapshotSpan(), null);
+        issueVisualizationWithNotEmptySpan.IsSuppressed = true;
 
-            testSubject.Should().BeAssignableTo<IFilterableIssue>();
+        VerifyPropertyChangedRaised(propertyChangedEventHandler, issueVisualizationWithNotEmptySpan, nameof(issueVisualizationWithNotEmptySpan.IsSuppressed));
+        propertyChangedEventHandler.ReceivedCalls().Count().Should().Be(1);
+        issueVisualizationWithNotEmptySpan.IsSuppressed.Should().BeTrue();
+    }
 
-            var filterable = (IFilterableIssue)testSubject;
+    [TestMethod]
+    public void IsFilterable()
+    {
+        issueVisualizationWithEmptySpan.Should().BeAssignableTo<IFilterableIssue>();
+
+        var filterable = (IFilterableIssue)issueVisualizationWithEmptySpan;
+        filterable.IssueId.Should().Be(issue.Id);
+        filterable.RuleId.Should().Be(issue.RuleKey);
+        filterable.FilePath.Should().Be(issue.PrimaryLocation.FilePath);
+        filterable.StartLine.Should().Be(issue.PrimaryLocation.TextRange.StartLine);
+        filterable.LineHash.Should().Be(issue.PrimaryLocation.TextRange.LineHash);
+    }
 
-            filterable.IssueId.Should().Be(id);
-            filterable.RuleId.Should().Be(issueMock.Object.RuleKey);
-            filterable.FilePath.Should().Be(issueMock.Object.PrimaryLocation.FilePath);
-            filterable.StartLine.Should().Be(issueMock.Object.PrimaryLocation.TextRange.StartLine);
-            filterable.LineHash.Should().Be(issueMock.Object.PrimaryLocation.TextRange.LineHash);
-        }
+    private void MockAnalysisIssue()
+    {
+        var id = Guid.NewGuid();
+        issue.Id.Returns(id);
+        issue.RuleKey.Returns("my key");
+        issue.PrimaryLocation.FilePath.Returns("x:\\aaa.foo");
+        issue.PrimaryLocation.TextRange.StartLine.Returns(999);
+        issue.PrimaryLocation.TextRange.LineHash.Returns("hash");
+        issue.PrimaryLocation.FilePath.Returns(filePath);
+    }
 
-        private SnapshotSpan CreateSpan()
-        {
-            var mockTextSnapshot = new Mock<ITextSnapshot>();
-            mockTextSnapshot.SetupGet(x => x.Length).Returns(20);
+    private SnapshotSpan CreateSpan()
+    {
+        var mockTextSnapshot = Substitute.For<ITextSnapshot>();
+        mockTextSnapshot.Length.Returns(20);
 
-            return new SnapshotSpan(mockTextSnapshot.Object, new Span(0, 10));
-        }
+        return new SnapshotSpan(mockTextSnapshot, new Span(0, 10));
+    }
 
-        private AnalysisIssueVisualization CreateTestSubject(string filePath = null, SnapshotSpan? span = null)
-        {
-            var issue = new Mock<IAnalysisIssue>();
-            issue.SetupGet(x => x.PrimaryLocation.FilePath).Returns(filePath);
+    private void VerifyPropertyChangedRaised(PropertyChangedEventHandler propertyChangedEventHandler, AnalysisIssueVisualization testSubject, string propertyName) =>
+        propertyChangedEventHandler.Received().Invoke(testSubject, Arg.Is<PropertyChangedEventArgs>(x => x.PropertyName == propertyName));
 
-            return new AnalysisIssueVisualization(null, issue.Object, span, null);
-        }
+    private void VerifyPropertyChangedNotRaised(PropertyChangedEventHandler propertyChangedEventHandler, string propertyName) =>
+        propertyChangedEventHandler.DidNotReceive().Invoke(Arg.Any<object>(), Arg.Is<PropertyChangedEventArgs>(x => x.PropertyName == propertyName));
 
-        private void VerifyPropertyChangedRaised(Mock<PropertyChangedEventHandler> propertyChangedEventHandler, string propertyName)
-        {
-            propertyChangedEventHandler.Verify(x =>
-                    x(It.IsAny<object>(), It.Is((PropertyChangedEventArgs e) => e.PropertyName == propertyName)),
-                Times.Once);
-        }
+    private PropertyChangedEventHandler MockSubscriberToPropertyChanged()
+    {
+        var propertyChangedEventHandler = Substitute.For<PropertyChangedEventHandler>();
+        issueVisualizationWithNotEmptySpan.PropertyChanged += propertyChangedEventHandler;
+        return propertyChangedEventHandler;
+    }
 
-        private void VerifyPropertyChangedNotRaised(Mock<PropertyChangedEventHandler> propertyChangedEventHandler, string propertyName)
-        {
-            propertyChangedEventHandler.Verify(x =>
-                    x(It.IsAny<object>(), It.Is((PropertyChangedEventArgs e) => e.PropertyName == propertyName)),
-                Times.Never);
-        }
+    private void VerifySpanAndLocationCorrect(string oldFilePath)
+    {
+        issueVisualizationWithNotEmptySpan.Span.Should().Be(notEmptySpan);
+        issueVisualizationWithNotEmptySpan.CurrentFilePath.Should().Be(oldFilePath);
     }
 }
diff --git a/src/IssueViz/Models/AnalysisIssueVisualization.cs b/src/IssueViz/Models/AnalysisIssueVisualization.cs
index 7facf0c2e7..15b5f37caa 100644
--- a/src/IssueViz/Models/AnalysisIssueVisualization.cs
+++ b/src/IssueViz/Models/AnalysisIssueVisualization.cs
@@ -18,134 +18,126 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-using System.Collections.Generic;
 using System.ComponentModel;
-using System.Linq;
 using System.Runtime.CompilerServices;
 using Microsoft.VisualStudio.Text;
 using SonarLint.VisualStudio.Core.Analysis;
 using SonarLint.VisualStudio.Core.Suppressions;
 
-namespace SonarLint.VisualStudio.IssueVisualization.Models
+namespace SonarLint.VisualStudio.IssueVisualization.Models;
+
+public interface IAnalysisIssueVisualization : IAnalysisIssueLocationVisualization, IFilterableIssue
 {
-    public interface IAnalysisIssueVisualization : IAnalysisIssueLocationVisualization, IFilterableIssue
-    {
-        IReadOnlyList<IAnalysisIssueFlowVisualization> Flows { get; }
+    IReadOnlyList<IAnalysisIssueFlowVisualization> Flows { get; }
 
-        IAnalysisIssueBase Issue { get; }
+    IAnalysisIssueBase Issue { get; }
 
-        IReadOnlyList<IQuickFixVisualization> QuickFixes { get; }
+    IReadOnlyList<IQuickFixVisualization> QuickFixes { get; }
 
-        bool IsSuppressed { get; set; }
-    }
+    bool IsSuppressed { get; set; }
+}
 
-    internal class AnalysisIssueVisualization : IAnalysisIssueVisualization
+internal class AnalysisIssueVisualization : IAnalysisIssueVisualization
+{
+    private static readonly SnapshotSpan EmptySpan = new();
+    private string currentFilePath;
+    private bool isSuppressed;
+    private SnapshotSpan? span;
+
+    public AnalysisIssueVisualization(
+        IReadOnlyList<IAnalysisIssueFlowVisualization> flows,
+        IAnalysisIssueBase issue,
+        SnapshotSpan? span,
+        IReadOnlyList<IQuickFixVisualization> quickFixes)
     {
-        private static readonly SnapshotSpan EmptySpan = new SnapshotSpan();
-        private string currentFilePath;
-        private SnapshotSpan? span;
-        private bool isSuppressed;
-
-        public AnalysisIssueVisualization(IReadOnlyList<IAnalysisIssueFlowVisualization> flows,
-            IAnalysisIssueBase issue, 
-            SnapshotSpan? span,
-            IReadOnlyList<IQuickFixVisualization> quickFixes)
-        {
-            Flows = flows;
-            Issue = issue;
-            CurrentFilePath = issue.PrimaryLocation.FilePath;
-            Span = span;
-            QuickFixes = quickFixes;
-        }
+        Flows = flows;
+        Issue = issue;
+        CurrentFilePath = issue.PrimaryLocation.FilePath;
+        Span = span;
+        QuickFixes = quickFixes;
+    }
 
-        public IReadOnlyList<IAnalysisIssueFlowVisualization> Flows { get; }
-        public IReadOnlyList<IQuickFixVisualization> QuickFixes { get; }
-        public IAnalysisIssueBase Issue { get; }
-        public int StepNumber => 0;
-        public IAnalysisIssueLocation Location => Issue.PrimaryLocation;
+    public IReadOnlyList<IAnalysisIssueFlowVisualization> Flows { get; }
+    public IReadOnlyList<IQuickFixVisualization> QuickFixes { get; }
+    public IAnalysisIssueBase Issue { get; }
+    public int StepNumber => 0;
+    public IAnalysisIssueLocation Location => Issue.PrimaryLocation;
 
-        public SnapshotSpan? Span
+    public SnapshotSpan? Span
+    {
+        get => span;
+        set
         {
-            get => span;
-            set
-            {
-                span = value;
-                NotifyPropertyChanged();
-            }
+            span = value;
+            NotifyPropertyChanged();
         }
+    }
 
-        public bool IsSuppressed
+    public bool IsSuppressed
+    {
+        get => isSuppressed;
+        set
         {
-            get => isSuppressed;
-            set
-            {
-                isSuppressed = value;
-                NotifyPropertyChanged();
-            }
+            isSuppressed = value;
+            NotifyPropertyChanged();
         }
+    }
 
-        public string CurrentFilePath
+    public string CurrentFilePath
+    {
+        get => currentFilePath;
+        set
         {
-            get => currentFilePath;
-            set
+            currentFilePath = value;
+
+            if (string.IsNullOrEmpty(currentFilePath))
             {
-                currentFilePath = value;
-
-                if (string.IsNullOrEmpty(currentFilePath))
-                {
-                    Span = EmptySpan;
-                }
-            
-                NotifyPropertyChanged();
+                Span = EmptySpan;
             }
+
+            NotifyPropertyChanged();
         }
+    }
 
-        public event PropertyChangedEventHandler PropertyChanged;
+    public event PropertyChangedEventHandler PropertyChanged;
 
-        protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
-        {
-            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
-        }
+    public Guid? IssueId => Issue.Id;
+    string IFilterableIssue.RuleId => Issue.RuleKey;
 
-        public Guid? IssueId => Issue.Id;
-        string IFilterableIssue.RuleId => Issue.RuleKey;
+    string IFilterableIssue.FilePath => CurrentFilePath;
 
-        string IFilterableIssue.FilePath => CurrentFilePath;
+    string IFilterableIssue.LineHash => Issue.PrimaryLocation.TextRange.LineHash;
 
-        string IFilterableIssue.LineHash => Issue.PrimaryLocation.TextRange.LineHash;
+    int? IFilterableIssue.StartLine => Issue.PrimaryLocation.TextRange.StartLine;
 
-        int? IFilterableIssue.StartLine => Issue.PrimaryLocation.TextRange.StartLine;
-    }
+    protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+}
 
-    public static class AnalysisIssueVisualizationExtensions
+public static class AnalysisIssueVisualizationExtensions
+{
+    /// <summary>
+    ///     Returns primary and all secondary locations of the given <see cref="issueVisualization" />
+    /// </summary>
+    public static IEnumerable<IAnalysisIssueLocationVisualization> GetAllLocations(this IAnalysisIssueVisualization issueVisualization)
     {
-        /// <summary>
-        /// Returns primary and all secondary locations of the given <see cref="issueVisualization"/>
-        /// </summary>
-        public static IEnumerable<IAnalysisIssueLocationVisualization> GetAllLocations(this IAnalysisIssueVisualization issueVisualization)
-        {
-            var primaryLocation = issueVisualization;
-            var secondaryLocations = issueVisualization.GetSecondaryLocations();
+        var primaryLocation = issueVisualization;
+        var secondaryLocations = issueVisualization.GetSecondaryLocations();
 
-            var allLocations = new List<IAnalysisIssueLocationVisualization> {primaryLocation};
-            allLocations.AddRange(secondaryLocations);
+        var allLocations = new List<IAnalysisIssueLocationVisualization> { primaryLocation };
+        allLocations.AddRange(secondaryLocations);
 
-            return allLocations;
-        }
-
-        /// <summary>
-        /// Returns all secondary locations of the given <see cref="issueVisualization"/>
-        /// </summary>
-        public static IEnumerable<IAnalysisIssueLocationVisualization> GetSecondaryLocations(this IAnalysisIssueVisualization issueVisualization)
-        {
-            var secondaryLocations = issueVisualization.Flows.SelectMany(x => x.Locations);
+        return allLocations;
+    }
 
-            return secondaryLocations;
-        }
+    /// <summary>
+    ///     Returns all secondary locations of the given <see cref="issueVisualization" />
+    /// </summary>
+    public static IEnumerable<IAnalysisIssueLocationVisualization> GetSecondaryLocations(this IAnalysisIssueVisualization issueVisualization)
+    {
+        var secondaryLocations = issueVisualization.Flows.SelectMany(x => x.Locations);
 
-        public static bool IsFileLevel(this IAnalysisIssueVisualization issueVisualization)
-        {
-            return issueVisualization.Issue.IsFileLevel();
-        }
+        return secondaryLocations;
     }
+
+    public static bool IsFileLevel(this IAnalysisIssueVisualization issueVisualization) => issueVisualization.Issue.IsFileLevel();
 }

From 90b145548c83dbc18a2e537c52758520a925aeb9 Mon Sep 17 00:00:00 2001
From: Gabriela Trutan <gabriela.trutan@sonarsource.com>
Date: Fri, 15 Nov 2024 13:41:08 +0100
Subject: [PATCH 6/6] SLVS-1625 Apply review feedback

---
 ...t.VisualStudio.Integration.sln.DotSettings |  7 ++-
 src/Education.UnitTests/EducationTests.cs     | 27 ++++----
 ...narErrorListEventProcessorProviderTests.cs | 40 +++++-------
 .../SonarErrorListEventProcessorTests.cs      |  2 +-
 .../Rule/SLCoreRuleMetaDataProviderTests.cs   | 27 ++++----
 .../SonarErrorListEventProcessorProvider.cs   | 62 +++++++++----------
 6 files changed, 81 insertions(+), 84 deletions(-)

diff --git a/SonarLint.VisualStudio.Integration.sln.DotSettings b/SonarLint.VisualStudio.Integration.sln.DotSettings
index 5f03c02e18..cc86674657 100644
--- a/SonarLint.VisualStudio.Integration.sln.DotSettings
+++ b/SonarLint.VisualStudio.Integration.sln.DotSettings
@@ -132,4 +132,9 @@
 	<s:String x:Key="/Default/CodeStyle/Generate/=Implementations/Options/=Async/@EntryIndexedValue">False</s:String>
 	<s:String x:Key="/Default/CodeStyle/Generate/=Implementations/Options/=Mutable/@EntryIndexedValue">False</s:String>
 	<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpAutoNaming/IsNotificationDisabled/@EntryValue">True</s:Boolean>
-	<s:Boolean x:Key="/Default/Dpa/IsNoEtwHostNotificationEnabled/@EntryValue">False</s:Boolean></wpf:ResourceDictionary>
\ No newline at end of file
+	<s:Boolean x:Key="/Default/Dpa/IsNoEtwHostNotificationEnabled/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002EMemberReordering_002EMigrations_002ECSharpFileLayoutPatternRemoveIsAttributeUpgrade/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
\ No newline at end of file
diff --git a/src/Education.UnitTests/EducationTests.cs b/src/Education.UnitTests/EducationTests.cs
index dec87b3078..d7a452d2cd 100644
--- a/src/Education.UnitTests/EducationTests.cs
+++ b/src/Education.UnitTests/EducationTests.cs
@@ -19,6 +19,7 @@
  */
 
 using System.Windows.Documents;
+using NSubstitute.ReturnsExtensions;
 using SonarLint.VisualStudio.Core;
 using SonarLint.VisualStudio.Education.Rule;
 using SonarLint.VisualStudio.Education.XamlGenerator;
@@ -53,7 +54,8 @@ public void TestInitialize()
         ruleInfo = Substitute.For<IRuleInfo>();
         logger = new TestLogger(true);
         threadHandling = new NoOpThreadHandler();
-        GetRuleInfoAsync(ruleInfo);
+        SetupKnownRule();
+        SetupUnknownRule();
 
         testSubject = new Education(toolWindowService, ruleMetadataProvider, showRuleInBrowser, logger, ruleHelpXamlBuilder, threadHandling);
     }
@@ -67,15 +69,21 @@ public void MefCtor_CheckIsExported() =>
             MefTestHelpers.CreateExport<IRuleHelpXamlBuilder>(),
             MefTestHelpers.CreateExport<ILogger>());
 
+    [TestMethod]
+    public void Ctor_IsFreeThreaded()
+    {
+        toolWindowService.ReceivedCalls().Should().HaveCount(0);
+        ruleMetadataProvider.ReceivedCalls().Should().HaveCount(0);
+        showRuleInBrowser.ReceivedCalls().Should().HaveCount(0);
+        ruleHelpXamlBuilder.ReceivedCalls().Should().HaveCount(0);
+    }
+
     [TestMethod]
     public void ShowRuleHelp_KnownRule_DocumentIsDisplayedInToolWindow()
     {
         var flowDocument = MockFlowDocument();
         toolWindowService.GetToolWindow<RuleHelpToolWindow, IRuleHelpToolWindow>().Returns(ruleDescriptionToolWindow);
-        // Sanity check - tool window not yet fetched
-        toolWindowService.ReceivedCalls().Should().HaveCount(0);
 
-        // Act
         testSubject.ShowRuleHelp(knownRule, null, null);
 
         VerifyGetsRuleInfoForCorrectRuleId(knownRule);
@@ -87,7 +95,6 @@ public void ShowRuleHelp_KnownRule_DocumentIsDisplayedInToolWindow()
     public void ShowRuleHelp_FailedToDisplayRule_RuleIsShownInBrowser()
     {
         ruleHelpXamlBuilder.When(x => x.Create(ruleInfo, /* todo by SLVS-1630 */ null)).Do(x => throw new Exception("some layout error"));
-        toolWindowService.ClearReceivedCalls(); // Called in the constructor, so need to reset to clear the list of invocations
 
         testSubject.ShowRuleHelp(knownRule, null, /* todo by SLVS-1630 */ null);
 
@@ -99,9 +106,6 @@ public void ShowRuleHelp_FailedToDisplayRule_RuleIsShownInBrowser()
     [TestMethod]
     public void ShowRuleHelp_UnknownRule_RuleIsShownInBrowser()
     {
-        GetRuleInfoAsync(null);
-        toolWindowService.ClearReceivedCalls(); // Called in the constructor, so need to reset to clear the list of invocations
-
         testSubject.ShowRuleHelp(unknownRule, null, /* todo by SLVS-1630 */ null);
 
         VerifyGetsRuleInfoForCorrectRuleId(unknownRule);
@@ -113,15 +117,12 @@ public void ShowRuleHelp_UnknownRule_RuleIsShownInBrowser()
     public void ShowRuleHelp_FilterableIssueProvided_CallsGetRuleInfoForIssue()
     {
         var issueId = Guid.NewGuid();
-        GetRuleInfoAsync(null);
 
         testSubject.ShowRuleHelp(knownRule, issueId, null);
 
         ruleMetadataProvider.Received(1).GetRuleInfoAsync(knownRule, issueId);
     }
 
-    private void GetRuleInfoAsync(IRuleInfo returnedRuleInfo) => ruleMetadataProvider.GetRuleInfoAsync(Arg.Any<SonarCompositeRuleId>(), Arg.Any<Guid?>()).Returns(returnedRuleInfo);
-
     private void VerifyGetsRuleInfoForCorrectRuleId(SonarCompositeRuleId ruleId) => ruleMetadataProvider.Received(1).GetRuleInfoAsync(ruleId, Arg.Any<Guid?>());
 
     private void VerifyRuleShownInBrowser(SonarCompositeRuleId ruleId) => showRuleInBrowser.Received(1).ShowRuleDescription(ruleId);
@@ -149,6 +150,10 @@ private void VerifyRuleIsDisplayedInIde(FlowDocument flowDocument)
         VerifyToolWindowShown();
     }
 
+    private void SetupKnownRule() => ruleMetadataProvider.GetRuleInfoAsync(knownRule, Arg.Any<Guid?>()).Returns(ruleInfo);
+
+    private void SetupUnknownRule() => ruleMetadataProvider.GetRuleInfoAsync(unknownRule, Arg.Any<Guid?>()).ReturnsNull();
+
     private FlowDocument MockFlowDocument()
     {
         var flowDocument = Substitute.For<FlowDocument>();
diff --git a/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorProviderTests.cs b/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorProviderTests.cs
index e6a761f543..75b6199b45 100644
--- a/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorProviderTests.cs
+++ b/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorProviderTests.cs
@@ -18,37 +18,31 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-using FluentAssertions;
 using Microsoft.VisualStudio.Shell.TableControl;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Moq;
 using SonarLint.VisualStudio.Core;
-using SonarLint.VisualStudio.Education.SonarLint.VisualStudio.Education.ErrorList;
+using SonarLint.VisualStudio.Education.ErrorList;
 using SonarLint.VisualStudio.Infrastructure.VS;
 using SonarLint.VisualStudio.TestInfrastructure;
 
-namespace SonarLint.VisualStudio.Education.UnitTests.ErrorList
+namespace SonarLint.VisualStudio.Education.UnitTests.ErrorList;
+
+[TestClass]
+public class SonarErrorListEventProcessorProviderTests
 {
-    [TestClass]
-    public class SonarErrorListEventProcessorProviderTests
-    {
-        [TestMethod]
-        public void MefCtor_CheckIsExported()
-        {
-            MefTestHelpers.CheckTypeCanBeImported<SonarErrorListEventProcessorProvider, ITableControlEventProcessorProvider>(
-                MefTestHelpers.CreateExport<IEducation>(),
-                MefTestHelpers.CreateExport<IErrorListHelper>(),
-                MefTestHelpers.CreateExport<ILogger>());
-        }
+    [TestMethod]
+    public void MefCtor_CheckIsExported() =>
+        MefTestHelpers.CheckTypeCanBeImported<SonarErrorListEventProcessorProvider, ITableControlEventProcessorProvider>(
+            MefTestHelpers.CreateExport<IEducation>(),
+            MefTestHelpers.CreateExport<IErrorListHelper>(),
+            MefTestHelpers.CreateExport<ILogger>());
 
-        [TestMethod]
-        public void Get_CreatesAndReturnsProcessor()
-        {
-            var testSubject = new SonarErrorListEventProcessorProvider(Mock.Of<IEducation>(), Mock.Of<IErrorListHelper>(), Mock.Of<ILogger>());
+    [TestMethod]
+    public void Get_CreatesAndReturnsProcessor()
+    {
+        var testSubject = new SonarErrorListEventProcessorProvider(Substitute.For<IEducation>(), Substitute.For<IErrorListHelper>(), Substitute.For<ILogger>());
 
-            var actual = testSubject.GetAssociatedEventProcessor(Mock.Of<IWpfTableControl>());
+        var actual = testSubject.GetAssociatedEventProcessor(Substitute.For<IWpfTableControl>());
 
-            actual.Should().NotBeNull();
-        }
+        actual.Should().NotBeNull();
     }
 }
diff --git a/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs b/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs
index d127f9a25b..0ed066c36c 100644
--- a/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs
+++ b/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs
@@ -46,7 +46,7 @@ public void TestInitialize()
         errorListHelper = Substitute.For<IErrorListHelper>();
         handle = Substitute.For<ITableEntryHandle>();
         filterableIssue = Substitute.For<IFilterableIssue>();
-        logger = new TestLogger(true);
+        logger = new TestLogger();
 
         testSubject = new SonarErrorListEventProcessor(education, errorListHelper, logger);
     }
diff --git a/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs b/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs
index b7fdd3b2ce..8e770ee2a6 100644
--- a/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs
+++ b/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs
@@ -128,7 +128,7 @@ public async Task GetRuleInfoAsync_ForIssue_IssueServiceUnavailable_ReturnsResul
 
         ruleInfo.Should().NotBeNull();
         logger.AssertNoOutputMessages();
-        await VerifyGetRuleDetailsWasCalled(compositeRuleId.ToString());
+        VerifyGetRuleDetailsWasCalled(compositeRuleId.ToString());
     }
 
     [TestMethod]
@@ -147,8 +147,8 @@ public async Task GetRuleInfoAsync_IssueIdNull_CallsGetEffectiveRuleDetailsAsync
     {
         await testSubject.GetRuleInfoAsync(compositeRuleId, null);
 
-        await VerifyGetRuleDetailsWasCalled(compositeRuleId.ToString());
-        await VerifyIssueDetailsWasNotCalled();
+        VerifyGetRuleDetailsWasCalled(compositeRuleId.ToString());
+        VerifyIssueDetailsWasNotCalled();
     }
 
     [TestMethod]
@@ -158,8 +158,8 @@ public async Task GetRuleInfoAsync_IssueIdNotNull_CallsGetEffectiveIssueDetailsA
 
         await testSubject.GetRuleInfoAsync(compositeRuleId, issueId);
 
-        await VerifyRuleDetailsWasNotCalled();
-        await VerifyGetIssueDetailsWasCalled(issueId);
+        VerifyRuleDetailsWasNotCalled();
+        VerifyGetIssueDetailsWasCalled(issueId);
     }
 
     [TestMethod]
@@ -169,8 +169,8 @@ public async Task GetRuleInfoAsync_GetEffectiveIssueDetailsAsyncThrows_CallsGetE
 
         await testSubject.GetRuleInfoAsync(compositeRuleId, issueId);
 
-        await VerifyGetRuleDetailsWasCalled(compositeRuleId.ToString());
-        await VerifyGetIssueDetailsWasCalled(issueId);
+        VerifyGetRuleDetailsWasCalled(compositeRuleId.ToString());
+        VerifyGetIssueDetailsWasCalled(issueId);
     }
 
     [TestMethod]
@@ -182,8 +182,8 @@ public async Task GetRuleInfoAsync_BothServicesThrow_ReturnsNull()
         var result = await testSubject.GetRuleInfoAsync(compositeRuleId, issueId);
 
         result.Should().BeNull();
-        await VerifyGetRuleDetailsWasCalled(compositeRuleId.ToString());
-        await VerifyGetIssueDetailsWasCalled(issueId);
+        VerifyGetRuleDetailsWasCalled(compositeRuleId.ToString());
+        VerifyGetIssueDetailsWasCalled(issueId);
     }
 
     private void SetUpConfigScopeTracker(ConfigurationScope scope) => configScopeTrackerMock.Current.Returns(scope);
@@ -224,12 +224,11 @@ private void MockupServices()
 
     private void MockRuleInfoConverter() => ruleInfoConverter.Convert(Arg.Any<IRuleDetails>()).Returns(defaultRuleInfo);
 
-    private async Task VerifyGetIssueDetailsWasCalled(Guid id) => await issueServiceMock.Received(1).GetEffectiveIssueDetailsAsync(Arg.Is<GetEffectiveIssueDetailsParams>(x => x.issueId == id));
+    private void VerifyGetIssueDetailsWasCalled(Guid id) => issueServiceMock.Received(1).GetEffectiveIssueDetailsAsync(Arg.Is<GetEffectiveIssueDetailsParams>(x => x.issueId == id));
 
-    private async Task VerifyGetRuleDetailsWasCalled(string ruleKey) =>
-        await rulesServiceMock.Received(1).GetEffectiveRuleDetailsAsync(Arg.Is<GetEffectiveRuleDetailsParams>(x => x.ruleKey == ruleKey));
+    private void VerifyGetRuleDetailsWasCalled(string ruleKey) => rulesServiceMock.Received(1).GetEffectiveRuleDetailsAsync(Arg.Is<GetEffectiveRuleDetailsParams>(x => x.ruleKey == ruleKey));
 
-    private async Task VerifyIssueDetailsWasNotCalled() => await issueServiceMock.DidNotReceive().GetEffectiveIssueDetailsAsync(Arg.Any<GetEffectiveIssueDetailsParams>());
+    private void VerifyIssueDetailsWasNotCalled() => issueServiceMock.DidNotReceive().GetEffectiveIssueDetailsAsync(Arg.Any<GetEffectiveIssueDetailsParams>());
 
-    private async Task VerifyRuleDetailsWasNotCalled() => await rulesServiceMock.DidNotReceive().GetEffectiveRuleDetailsAsync(Arg.Any<GetEffectiveRuleDetailsParams>());
+    private void VerifyRuleDetailsWasNotCalled() => rulesServiceMock.DidNotReceive().GetEffectiveRuleDetailsAsync(Arg.Any<GetEffectiveRuleDetailsParams>());
 }
diff --git a/src/Education/ErrorList/SonarErrorListEventProcessorProvider.cs b/src/Education/ErrorList/SonarErrorListEventProcessorProvider.cs
index 64f35a551c..f4deed3a94 100644
--- a/src/Education/ErrorList/SonarErrorListEventProcessorProvider.cs
+++ b/src/Education/ErrorList/SonarErrorListEventProcessorProvider.cs
@@ -22,46 +22,40 @@
 using Microsoft.VisualStudio.Shell.TableControl;
 using Microsoft.VisualStudio.Utilities;
 using SonarLint.VisualStudio.Core;
-using SonarLint.VisualStudio.Education.ErrorList;
 using SonarLint.VisualStudio.Infrastructure.VS;
 
-namespace SonarLint.VisualStudio.Education
-{
-    namespace SonarLint.VisualStudio.Education.ErrorList
-    {
-        // Notifies VS that we want to handle events from the Error List
+namespace SonarLint.VisualStudio.Education.ErrorList;
+// Notifies VS that we want to handle events from the Error List
 
-        [Export(typeof(ITableControlEventProcessorProvider))]
-        [Name("SonarLint ErrorList Event Processor")]
+[Export(typeof(ITableControlEventProcessorProvider))]
+[Name("SonarLint ErrorList Event Processor")]
 
-        // Need to hook into the list of processors before the standard VS handler so we can
-        // change the behaviour of the "navigate to help" action
-        [Order(After = "Default Priority", Before = "ErrorListPackage Table Control Event Processor")]
-        [ManagerType("ErrorsTable")]
+// Need to hook into the list of processors before the standard VS handler so we can
+// change the behaviour of the "navigate to help" action
+[Order(After = "Default Priority", Before = "ErrorListPackage Table Control Event Processor")]
+[ManagerType("ErrorsTable")]
 
-        // TODO - DataSourceType/DataSource can both be used multiple times. Can we just register for our source and Roslyn?
-        // Ideally, we'd only handle our own data source types. However, we also need to handle the Roslyn data source
-        [DataSourceType("*")]
-        [DataSource("*")]
-        internal class SonarErrorListEventProcessorProvider : ITableControlEventProcessorProvider
-        {
-            private readonly IEducation educationService;
-            private readonly IErrorListHelper errorListHelper;
-            private readonly ILogger logger;
+// TODO - DataSourceType/DataSource can both be used multiple times. Can we just register for our source and Roslyn?
+// Ideally, we'd only handle our own data source types. However, we also need to handle the Roslyn data source
+[DataSourceType("*")]
+[DataSource("*")]
+internal class SonarErrorListEventProcessorProvider : ITableControlEventProcessorProvider
+{
+    private readonly IEducation educationService;
+    private readonly IErrorListHelper errorListHelper;
+    private readonly ILogger logger;
 
-            [ImportingConstructor]
-            public SonarErrorListEventProcessorProvider(IEducation educationService, IErrorListHelper errorListHelper, ILogger logger)
-            {
-                this.educationService = educationService;
-                this.errorListHelper = errorListHelper;
-                this.logger = logger;
-            }
+    [ImportingConstructor]
+    public SonarErrorListEventProcessorProvider(IEducation educationService, IErrorListHelper errorListHelper, ILogger logger)
+    {
+        this.educationService = educationService;
+        this.errorListHelper = errorListHelper;
+        this.logger = logger;
+    }
 
-            public ITableControlEventProcessor GetAssociatedEventProcessor(IWpfTableControl tableControl)
-            {
-                logger.LogVerbose(Resources.ErrorList_ProcessorCreated);
-                return new SonarErrorListEventProcessor(educationService, errorListHelper, logger);
-            }
-        }
+    public ITableControlEventProcessor GetAssociatedEventProcessor(IWpfTableControl tableControl)
+    {
+        logger.LogVerbose(Resources.ErrorList_ProcessorCreated);
+        return new SonarErrorListEventProcessor(educationService, errorListHelper, logger);
     }
 }