From 2f556e92e97943ff6874895bfeb8567d764e24d7 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Mon, 5 Aug 2024 10:08:21 +0200 Subject: [PATCH] SLVS-1292 Use vulnerabilityProbability from SLCore instead of the hardcoded mapping (#5573) --- src/Core/Analysis/AnalysisIssue.cs | 26 +- src/Core/Analysis/IAnalysisIssue.cs | 14 +- .../HotspotReviewPriorityProviderTests.cs | 12 +- .../HotspotsControlViewModelTests.cs | 7 +- .../PriorityToBackgroundConverterTests.cs | 5 +- .../Hotspots/LocalHostpotStoreMonitorTests.cs | 8 +- .../Hotspots/LocalHotspotStoreTests.cs | 44 +- .../Hotspots/Models/HotspotRuleTests.cs | 3 +- .../Hotspots/HotspotReviewPriorityProvider.cs | 132 +----- .../PriorityToBackgroundConverter.cs | 3 +- .../ViewModels/HotspotViewModel.cs | 2 +- .../Hotspots/LocalHotspot.cs | 3 +- .../Hotspots/LocalHotspotsStore.cs | 32 +- .../Hotspots/Models/IHotspotRule.cs | 9 +- .../Helpers/ModelConversionExtensionsTests.cs | 22 + ...iseFindingToAnalysisIssueConverterTests.cs | 421 ++++++++++++------ .../Helpers/ModelConversionExtensions.cs | 12 + .../RaiseFindingToAnalysisIssueConverter.cs | 45 +- 18 files changed, 459 insertions(+), 341 deletions(-) diff --git a/src/Core/Analysis/AnalysisIssue.cs b/src/Core/Analysis/AnalysisIssue.cs index f02e46a804..4fe9c3fb32 100644 --- a/src/Core/Analysis/AnalysisIssue.cs +++ b/src/Core/Analysis/AnalysisIssue.cs @@ -18,15 +18,12 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; - namespace SonarLint.VisualStudio.Core.Analysis { public class AnalysisIssue : IAnalysisIssue { - private static readonly IReadOnlyList EmptyFlows = Array.Empty(); - private static readonly IReadOnlyList EmptyFixes = Array.Empty(); + private static readonly IReadOnlyList EmptyFlows = []; + private static readonly IReadOnlyList EmptyFixes = []; public AnalysisIssue( string ruleKey, @@ -66,6 +63,25 @@ public AnalysisIssue( public string RuleDescriptionContextKey { get; } } + public class AnalysisHotspotIssue : AnalysisIssue, IAnalysisHotspotIssue + { + public AnalysisHotspotIssue(string ruleKey, + AnalysisIssueSeverity severity, + AnalysisIssueType type, + SoftwareQualitySeverity? highestSoftwareQualitySeverity, + IAnalysisIssueLocation primaryLocation, + IReadOnlyList flows, + IReadOnlyList fixes = null, + string context = null, + HotspotPriority? hotspotPriority = null) : + base(ruleKey, severity, type, highestSoftwareQualitySeverity, primaryLocation, flows, fixes, context) + { + HotspotPriority = hotspotPriority; + } + + public HotspotPriority? HotspotPriority { get; } + } + public class AnalysisIssueFlow : IAnalysisIssueFlow { public AnalysisIssueFlow(IReadOnlyList locations) diff --git a/src/Core/Analysis/IAnalysisIssue.cs b/src/Core/Analysis/IAnalysisIssue.cs index 40be207d0a..5a80e85007 100644 --- a/src/Core/Analysis/IAnalysisIssue.cs +++ b/src/Core/Analysis/IAnalysisIssue.cs @@ -18,8 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System.Collections.Generic; - namespace SonarLint.VisualStudio.Core.Analysis { public interface IAnalysisIssue : IAnalysisIssueBase @@ -33,6 +31,11 @@ public interface IAnalysisIssue : IAnalysisIssueBase IReadOnlyList Fixes { get; } } + public interface IAnalysisHotspotIssue : IAnalysisIssue + { + HotspotPriority? HotspotPriority { get; } + } + public interface IAnalysisIssueBase { string RuleKey { get; } @@ -106,6 +109,13 @@ public enum AnalysisIssueType SecurityHotspot } + public enum HotspotPriority + { + High, + Medium, + Low + } + public static class IAnalysisIssueExtensions { public static bool IsFileLevel(this IAnalysisIssueBase issue) diff --git a/src/IssueViz.Security.UnitTests/Hotspots/HotspotReviewPriorityProviderTests.cs b/src/IssueViz.Security.UnitTests/Hotspots/HotspotReviewPriorityProviderTests.cs index 502f05e5c6..99aea0041c 100644 --- a/src/IssueViz.Security.UnitTests/Hotspots/HotspotReviewPriorityProviderTests.cs +++ b/src/IssueViz.Security.UnitTests/Hotspots/HotspotReviewPriorityProviderTests.cs @@ -18,11 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots; -using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots.Models; using SonarLint.VisualStudio.TestInfrastructure; namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Hotspots; @@ -43,11 +40,6 @@ public void MefCtor_CheckIsSingleton() } [DataRow("notakey", null)] - [DataRow("typescript:S4502", HotspotPriority.High)] - [DataRow("javascript:S4502", HotspotPriority.High)] - [DataRow("typescript:S1313", HotspotPriority.Low)] - [DataRow("javascript:S1313", HotspotPriority.Low)] - [DataRow("javascript:S90000000000000000", null)] [DataRow("c:S1313", HotspotPriority.Low)] [DataRow("cpp:S1313", HotspotPriority.Low)] [DataRow("python:S1313", null)] @@ -58,7 +50,7 @@ public void GetPriority_ShouldReturnAsExpected(string ruleKey, HotspotPriority? { new HotspotReviewPriorityProvider().GetPriority(ruleKey).Should().Be(expectedPriority); } - + [TestMethod] public void GetPriority_NullKey_Throws() { diff --git a/src/IssueViz.Security.UnitTests/Hotspots/HotspotsList/HotspotsControlViewModelTests.cs b/src/IssueViz.Security.UnitTests/Hotspots/HotspotsList/HotspotsControlViewModelTests.cs index 2dbd66b08f..4f006e49f6 100644 --- a/src/IssueViz.Security.UnitTests/Hotspots/HotspotsList/HotspotsControlViewModelTests.cs +++ b/src/IssueViz.Security.UnitTests/Hotspots/HotspotsList/HotspotsControlViewModelTests.cs @@ -18,22 +18,17 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; using System.Collections.ObjectModel; using System.ComponentModel; -using System.Linq; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.TestInfrastructure; using SonarLint.VisualStudio.IssueVisualization.Editor; using SonarLint.VisualStudio.IssueVisualization.IssueVisualizationControl.ViewModels.Commands; using SonarLint.VisualStudio.IssueVisualization.Models; using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots; using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots.HotspotsList.ViewModels; -using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots.Models; using SonarLint.VisualStudio.IssueVisualization.Selection; namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Hotspots.HotspotsList diff --git a/src/IssueViz.Security.UnitTests/Hotspots/HotspotsList/PriorityToBackgroundConverterTests.cs b/src/IssueViz.Security.UnitTests/Hotspots/HotspotsList/PriorityToBackgroundConverterTests.cs index 75cd36a519..8eabfa176f 100644 --- a/src/IssueViz.Security.UnitTests/Hotspots/HotspotsList/PriorityToBackgroundConverterTests.cs +++ b/src/IssueViz.Security.UnitTests/Hotspots/HotspotsList/PriorityToBackgroundConverterTests.cs @@ -18,12 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; using System.Windows.Media; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots.HotspotsList; -using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots.Models; namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Hotspots.HotspotsList { diff --git a/src/IssueViz.Security.UnitTests/Hotspots/LocalHostpotStoreMonitorTests.cs b/src/IssueViz.Security.UnitTests/Hotspots/LocalHostpotStoreMonitorTests.cs index 35226f11ba..44a473835e 100644 --- a/src/IssueViz.Security.UnitTests/Hotspots/LocalHostpotStoreMonitorTests.cs +++ b/src/IssueViz.Security.UnitTests/Hotspots/LocalHostpotStoreMonitorTests.cs @@ -18,16 +18,12 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using FluentAssertions; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using SonarLint.VisualStudio.ConnectedMode.Hotspots; using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.IssueVisualization.Models; using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots; using SonarLint.VisualStudio.IssueVisualization.Security.IssuesStore; @@ -87,7 +83,7 @@ public async Task IssuesChanged_HasIssues_ContextIsSet() { var monitorSelection = CreateMonitorSelection(222); var serviceProvider = CreateServiceProvider(monitorSelection.Object); - var store = CreateStore(new LocalHotspot(Mock.Of(), Security.Hotspots.Models.HotspotPriority.Medium)); + var store = CreateStore(new LocalHotspot(Mock.Of(), HotspotPriority.Medium)); _ = await CreateInitializedTestSubject(serviceProvider.Object, store.Object); monitorSelection.Invocations.Clear(); diff --git a/src/IssueViz.Security.UnitTests/Hotspots/LocalHotspotStoreTests.cs b/src/IssueViz.Security.UnitTests/Hotspots/LocalHotspotStoreTests.cs index f006e5f752..6d658c9a9f 100644 --- a/src/IssueViz.Security.UnitTests/Hotspots/LocalHotspotStoreTests.cs +++ b/src/IssueViz.Security.UnitTests/Hotspots/LocalHotspotStoreTests.cs @@ -18,18 +18,14 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; -using System.Linq; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using SonarLint.VisualStudio.ConnectedMode.Hotspots; using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.IssueVisualization.Models; using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots; -using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots.Models; using SonarLint.VisualStudio.IssueVisualization.Security.IssuesStore; +using SonarLint.VisualStudio.SLCore.Common.Models; using SonarLint.VisualStudio.TestInfrastructure; using SonarQube.Client.Models; @@ -43,8 +39,8 @@ public void MefCtor_CheckExports_ILocalHotspotsStore() { MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); } @@ -53,8 +49,8 @@ public void MefCtor_CheckExports_ILocalHotspotsStoreUpdater() { MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); } @@ -63,8 +59,8 @@ public void MefCtor_CheckExports_IIssuesStore() { MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); } @@ -181,6 +177,23 @@ public void UpdateForFile_NoServerHotspots_UsesReviewPriority() new LocalHotspot(issueVis3.Object, HotspotPriority.Low)); } + [TestMethod] + [DataRow(HotspotPriority.High)] + [DataRow(HotspotPriority.Medium)] + [DataRow(HotspotPriority.Low)] + public void UpdateForFile_WithNoServerHotspots_ShouldAssignHotspotPriority(HotspotPriority priority) + { + const string rule1 = "rule:s1"; + var issueVis1 = CreateIssueVisualizationWithHotspot(rule1, priority); + var reviewPriorityProviderMock = new Mock(); + var testSubject = CreateTestSubject(out _, reviewPriorityProvider: reviewPriorityProviderMock.Object); + + testSubject.UpdateForFile("file1", new[] { issueVis1 }); + + VerifyContent(testSubject, new LocalHotspot(issueVis1, priority)); + reviewPriorityProviderMock.Verify(mock => mock.GetPriority(It.IsAny()), Times.Never); + } + [TestMethod] public void UpdateForFile_ServerHotspots_MatchesCorrectly() { @@ -210,7 +223,7 @@ public void UpdateForFile_ServerHotspots_MatchesCorrectly() } [TestMethod] - public void UpdateForFile_ServerHotspots_UsesReviewPriority() + public void UpdateForFile_WithNoServerHotspots_ForCFamily_ShouldAssignHotspotPriority() { /* * issue1 + server1 -> rule1 -> Low - could be changed to test server override once implemented @@ -625,4 +638,15 @@ private void EventHandler(object sender, IssuesChangedEventArgs eventArgs) Events.Add(eventArgs); } } + + private static IAnalysisIssueVisualization CreateIssueVisualizationWithHotspot(string rule, HotspotPriority priority) + { + var issueVis = new Mock(); + var hotspotIssue = new Mock(); + hotspotIssue.SetupGet(x => x.HotspotPriority).Returns(priority); + issueVis.Setup(x => x.Issue).Returns(hotspotIssue.Object); + issueVis.Setup(x => x.RuleId).Returns(rule); + + return issueVis.Object; + } } diff --git a/src/IssueViz.Security.UnitTests/Hotspots/Models/HotspotRuleTests.cs b/src/IssueViz.Security.UnitTests/Hotspots/Models/HotspotRuleTests.cs index 0de7f9f1f0..0a0d22741b 100644 --- a/src/IssueViz.Security.UnitTests/Hotspots/Models/HotspotRuleTests.cs +++ b/src/IssueViz.Security.UnitTests/Hotspots/Models/HotspotRuleTests.cs @@ -18,8 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots.Models; namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Hotspots.Models diff --git a/src/IssueViz.Security/Hotspots/HotspotReviewPriorityProvider.cs b/src/IssueViz.Security/Hotspots/HotspotReviewPriorityProvider.cs index 4dd0b48bc4..32bbc5b8c7 100644 --- a/src/IssueViz.Security/Hotspots/HotspotReviewPriorityProvider.cs +++ b/src/IssueViz.Security/Hotspots/HotspotReviewPriorityProvider.cs @@ -18,10 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; using System.ComponentModel.Composition; -using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots.Models; +using SonarLint.VisualStudio.Core.Analysis; namespace SonarLint.VisualStudio.IssueVisualization.Security.Hotspots { @@ -89,135 +87,9 @@ public class HotspotReviewPriorityProvider : IHotspotReviewPriorityProvider { "c:S5042", HotspotPriority.Low }, { "c:S5849", HotspotPriority.Medium }, { "c:S5982", HotspotPriority.Low }, - - { "javascript:S2092", HotspotPriority.Low }, - { "javascript:S6332", HotspotPriority.Low }, - { "javascript:S5122", HotspotPriority.Low }, - { "javascript:S6330", HotspotPriority.Low }, - { "javascript:S5247", HotspotPriority.High }, - { "javascript:S6333", HotspotPriority.Medium }, - { "javascript:S4036", HotspotPriority.Low }, - { "javascript:S4823", HotspotPriority.Low }, - { "javascript:S4829", HotspotPriority.Low }, - { "javascript:S6329", HotspotPriority.Medium }, - { "javascript:S6327", HotspotPriority.Low }, - { "javascript:S1313", HotspotPriority.Low }, - { "javascript:S5148", HotspotPriority.Low }, - { "javascript:S4721", HotspotPriority.High }, - { "javascript:S1523", HotspotPriority.Low }, - { "javascript:S2612", HotspotPriority.Medium }, - { "javascript:S5443", HotspotPriority.Low }, - { "javascript:S5689", HotspotPriority.Low }, - { "javascript:S6319", HotspotPriority.Low }, - { "javascript:S4818", HotspotPriority.Low }, - { "javascript:S4817", HotspotPriority.Low }, - { "javascript:S2077", HotspotPriority.High }, - { "javascript:S5693", HotspotPriority.Medium }, - { "javascript:S5691", HotspotPriority.Low }, - { "javascript:S6308", HotspotPriority.Low }, - { "javascript:S6302", HotspotPriority.Medium }, - { "javascript:S6303", HotspotPriority.Low }, - { "javascript:S2068", HotspotPriority.High }, - { "javascript:S5332", HotspotPriority.Low }, - { "javascript:S6304", HotspotPriority.Medium }, - { "javascript:S6299", HotspotPriority.High }, - { "javascript:S4790", HotspotPriority.Low }, - { "javascript:S2255", HotspotPriority.Low }, - { "javascript:S6281", HotspotPriority.Medium }, - { "javascript:S5759", HotspotPriority.Low }, - { "javascript:S4784", HotspotPriority.Low }, - { "javascript:S3330", HotspotPriority.Low }, - { "javascript:S5757", HotspotPriority.Low }, - { "javascript:S2245", HotspotPriority.Medium }, - { "javascript:S4787", HotspotPriority.Low }, - { "javascript:S6252", HotspotPriority.Low }, - { "javascript:S5042", HotspotPriority.Low }, - { "javascript:S5728", HotspotPriority.Low }, - { "javascript:S5725", HotspotPriority.Low }, - { "javascript:S5604", HotspotPriority.Low }, - { "javascript:S4507", HotspotPriority.Low }, - { "javascript:S6245", HotspotPriority.Low }, - { "javascript:S4502", HotspotPriority.High }, - { "javascript:S6249", HotspotPriority.Low }, - { "javascript:S6270", HotspotPriority.Medium }, - { "javascript:S6275", HotspotPriority.Low }, - { "javascript:S5742", HotspotPriority.Low }, - { "javascript:S5743", HotspotPriority.Low }, - { "javascript:S6265", HotspotPriority.Medium }, - { "javascript:S5739", HotspotPriority.Low }, - { "javascript:S5736", HotspotPriority.Low }, - { "javascript:S5730", HotspotPriority.Low }, - { "javascript:S5852", HotspotPriority.Medium }, - { "javascript:S6268", HotspotPriority.High }, - { "javascript:S5734", HotspotPriority.Low }, - { "javascript:S5732", HotspotPriority.Low }, - { "javascript:S6350", HotspotPriority.High }, - - { "typescript:S2092", HotspotPriority.Low }, - { "typescript:S6332", HotspotPriority.Low }, - { "typescript:S5122", HotspotPriority.Low }, - { "typescript:S6330", HotspotPriority.Low }, - { "typescript:S5247", HotspotPriority.High }, - { "typescript:S6333", HotspotPriority.Medium }, - { "typescript:S4036", HotspotPriority.Low }, - { "typescript:S4823", HotspotPriority.Low }, - { "typescript:S4829", HotspotPriority.Low }, - { "typescript:S6329", HotspotPriority.Medium }, - { "typescript:S6327", HotspotPriority.Low }, - { "typescript:S1313", HotspotPriority.Low }, - { "typescript:S5148", HotspotPriority.Low }, - { "typescript:S4721", HotspotPriority.High }, - { "typescript:S1523", HotspotPriority.Medium }, - { "typescript:S2612", HotspotPriority.Medium }, - { "typescript:S5443", HotspotPriority.Low }, - { "typescript:S5689", HotspotPriority.Low }, - { "typescript:S6319", HotspotPriority.Low }, - { "typescript:S4818", HotspotPriority.Low }, - { "typescript:S4817", HotspotPriority.Low }, - { "typescript:S2077", HotspotPriority.High }, - { "typescript:S5693", HotspotPriority.Medium }, - { "typescript:S5691", HotspotPriority.Low }, - { "typescript:S6308", HotspotPriority.Low }, - { "typescript:S6302", HotspotPriority.Medium }, - { "typescript:S6303", HotspotPriority.Low }, - { "typescript:S2068", HotspotPriority.High }, - { "typescript:S5332", HotspotPriority.Low }, - { "typescript:S6304", HotspotPriority.Medium }, - { "typescript:S6299", HotspotPriority.High }, - { "typescript:S4790", HotspotPriority.Low }, - { "typescript:S2255", HotspotPriority.Low }, - { "typescript:S6281", HotspotPriority.Medium }, - { "typescript:S5759", HotspotPriority.Low }, - { "typescript:S4784", HotspotPriority.Low }, - { "typescript:S3330", HotspotPriority.Low }, - { "typescript:S5757", HotspotPriority.Low }, - { "typescript:S2245", HotspotPriority.Medium }, - { "typescript:S4787", HotspotPriority.Low }, - { "typescript:S6252", HotspotPriority.Low }, - { "typescript:S5042", HotspotPriority.Low }, - { "typescript:S5728", HotspotPriority.Low }, - { "typescript:S5725", HotspotPriority.Low }, - { "typescript:S5604", HotspotPriority.Low }, - { "typescript:S4507", HotspotPriority.Low }, - { "typescript:S6245", HotspotPriority.Low }, - { "typescript:S4502", HotspotPriority.High }, - { "typescript:S6249", HotspotPriority.Low }, - { "typescript:S6270", HotspotPriority.Medium }, - { "typescript:S6275", HotspotPriority.Low }, - { "typescript:S5742", HotspotPriority.Low }, - { "typescript:S5743", HotspotPriority.Low }, - { "typescript:S6265", HotspotPriority.Medium }, - { "typescript:S5739", HotspotPriority.Low }, - { "typescript:S5736", HotspotPriority.Low }, - { "typescript:S5730", HotspotPriority.Low }, - { "typescript:S5852", HotspotPriority.Medium }, - { "typescript:S6268", HotspotPriority.High }, - { "typescript:S5734", HotspotPriority.Low }, - { "typescript:S5732", HotspotPriority.Low }, - { "typescript:S6350", HotspotPriority.High }, }; - public HotspotPriority? GetPriority(string hotspotKey) => + public HotspotPriority? GetPriority(string hotspotKey) => KeyToPriorityMap.TryGetValue(hotspotKey ?? throw new ArgumentNullException(nameof(hotspotKey)), out var priority) ? priority : (HotspotPriority?)null; } } diff --git a/src/IssueViz.Security/Hotspots/HotspotsList/PriorityToBackgroundConverter.cs b/src/IssueViz.Security/Hotspots/HotspotsList/PriorityToBackgroundConverter.cs index d7b8815dd5..d3f09442d7 100644 --- a/src/IssueViz.Security/Hotspots/HotspotsList/PriorityToBackgroundConverter.cs +++ b/src/IssueViz.Security/Hotspots/HotspotsList/PriorityToBackgroundConverter.cs @@ -18,11 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; using System.Globalization; using System.Windows.Data; using System.Windows.Media; -using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots.Models; +using SonarLint.VisualStudio.Core.Analysis; namespace SonarLint.VisualStudio.IssueVisualization.Security.Hotspots.HotspotsList { diff --git a/src/IssueViz.Security/Hotspots/HotspotsList/ViewModels/HotspotViewModel.cs b/src/IssueViz.Security/Hotspots/HotspotsList/ViewModels/HotspotViewModel.cs index 8caa6d4ce6..e120b54143 100644 --- a/src/IssueViz.Security/Hotspots/HotspotsList/ViewModels/HotspotViewModel.cs +++ b/src/IssueViz.Security/Hotspots/HotspotsList/ViewModels/HotspotViewModel.cs @@ -18,10 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; using System.ComponentModel; using System.IO; using System.Runtime.CompilerServices; +using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.IssueVisualization.Models; using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots.Models; diff --git a/src/IssueViz.Security/Hotspots/LocalHotspot.cs b/src/IssueViz.Security/Hotspots/LocalHotspot.cs index 226ccb68b8..e0783b8d36 100644 --- a/src/IssueViz.Security/Hotspots/LocalHotspot.cs +++ b/src/IssueViz.Security/Hotspots/LocalHotspot.cs @@ -18,9 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; +using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.IssueVisualization.Models; -using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots.Models; using SonarQube.Client.Models; namespace SonarLint.VisualStudio.IssueVisualization.Security.Hotspots diff --git a/src/IssueViz.Security/Hotspots/LocalHotspotsStore.cs b/src/IssueViz.Security/Hotspots/LocalHotspotsStore.cs index 900db521bc..70100f2ecb 100644 --- a/src/IssueViz.Security/Hotspots/LocalHotspotsStore.cs +++ b/src/IssueViz.Security/Hotspots/LocalHotspotsStore.cs @@ -18,15 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; using System.ComponentModel.Composition; -using System.Diagnostics; -using System.Linq; using SonarLint.VisualStudio.ConnectedMode.Hotspots; using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.IssueVisualization.Models; -using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots.Models; using SonarLint.VisualStudio.IssueVisualization.Security.IssuesStore; using SonarQube.Client.Models; @@ -58,19 +54,18 @@ internal interface ILocalHotspotsStore : ILocalHotspotsStoreUpdater, IIssuesStor [PartCreationPolicy(CreationPolicy.Shared)] internal sealed class LocalHotspotsStore : ILocalHotspotsStore { - private static readonly List EmptyList = new List(); + private static readonly List EmptyList = []; // on the off chance we can't map the RuleId to Priority, which shouldn't happen, it's better to raise it as High private static readonly HotspotPriority DefaultPriority = HotspotPriority.High; - private readonly object lockObject = new object(); + private readonly object lockObject = new(); private readonly IHotspotMatcher hotspotMatcher; private readonly IThreadHandling threadHandling; private readonly IServerHotspotStore serverHotspotStore; private readonly IHotspotReviewPriorityProvider hotspotReviewPriorityProvider; - private Dictionary> fileToHotspotsMapping = - new Dictionary>(); + private Dictionary> fileToHotspotsMapping = []; private ISet unmatchedHotspots = CreateServerHotspotSet(); @@ -221,12 +216,17 @@ private LocalHotspot MatchAndConvert(IAnalysisIssueVisualization visualization) } unmatchedHotspots.Remove(serverHotspot); - return new LocalHotspot(visualization, - hotspotReviewPriorityProvider.GetPriority(visualization.RuleId) ?? DefaultPriority, // todo: override with server priority - serverHotspot); + return new LocalHotspot(visualization, GetPriority(visualization), serverHotspot); } - return new LocalHotspot(visualization, hotspotReviewPriorityProvider.GetPriority(visualization.RuleId) ?? DefaultPriority); + return new LocalHotspot(visualization, GetPriority(visualization)); + } + + private HotspotPriority GetPriority(IAnalysisIssueVisualization visualization) + { + var mappedHotspotPriority = (visualization.Issue as IAnalysisHotspotIssue)?.HotspotPriority ?? hotspotReviewPriorityProvider.GetPriority(visualization.RuleId); + + return mappedHotspotPriority ?? DefaultPriority; } private IEnumerable GetOpenHotspots() => @@ -239,9 +239,7 @@ private void NotifyIssuesChanged(IssuesChangedEventArgs args) private static ISet CreateServerHotspotSet(IEnumerable serverHotspots = null) { - return new SortedSet( - serverHotspots ?? Enumerable.Empty(), - new ServerHotspotComparer()); + return new SortedSet(serverHotspots ?? [], new ServerHotspotComparer()); } /// @@ -250,7 +248,7 @@ private static ISet CreateServerHotspotSet(IEnumerable internal /* for testing */ class ServerHotspotComparer : IComparer { - private static IssueTextRange EmptyTextRange = new IssueTextRange(0, 0, 0, 0); + private static readonly IssueTextRange EmptyTextRange = new(0, 0, 0, 0); public int Compare(SonarQubeHotspot x, SonarQubeHotspot y) { Debug.Assert(x != null && y != null, "Not expecting either server hotspot to be null"); diff --git a/src/IssueViz.Security/Hotspots/Models/IHotspotRule.cs b/src/IssueViz.Security/Hotspots/Models/IHotspotRule.cs index bd9b642578..f6d0830a6a 100644 --- a/src/IssueViz.Security/Hotspots/Models/IHotspotRule.cs +++ b/src/IssueViz.Security/Hotspots/Models/IHotspotRule.cs @@ -18,15 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using SonarLint.VisualStudio.Core.Analysis; + namespace SonarLint.VisualStudio.IssueVisualization.Security.Hotspots.Models { - public enum HotspotPriority - { - High, - Medium, - Low - } - internal interface IHotspotRule { string RuleKey { get; } diff --git a/src/SLCore.UnitTests/Common/Helpers/ModelConversionExtensionsTests.cs b/src/SLCore.UnitTests/Common/Helpers/ModelConversionExtensionsTests.cs index 926602eb14..35163d10b4 100644 --- a/src/SLCore.UnitTests/Common/Helpers/ModelConversionExtensionsTests.cs +++ b/src/SLCore.UnitTests/Common/Helpers/ModelConversionExtensionsTests.cs @@ -119,5 +119,27 @@ Unexpected enum value Actual value was 1000. """); } + + [TestMethod] + [DataRow(null, null)] + [DataRow(VulnerabilityProbability.HIGH, HotspotPriority.High)] + [DataRow(VulnerabilityProbability.MEDIUM, HotspotPriority.Medium)] + [DataRow(VulnerabilityProbability.LOW, HotspotPriority.Low)] + public void GetHotspotPriority_HotspotHasVulnerabilityProbability_ConvertsCorrectly(VulnerabilityProbability? vulnerabilityProbability, HotspotPriority? expectedHotspotPriority) + { + var result = vulnerabilityProbability.GetHotspotPriority(); + + result.Should().Be(expectedHotspotPriority); + } + + [TestMethod] + public void GetHotspotPriority_ValueOutOfRange_Throws() + { + var vulnerabilityProbability = ((VulnerabilityProbability?)1000); + + var act = () => vulnerabilityProbability.GetHotspotPriority(); + + act.Should().Throw(); + } } } diff --git a/src/SLCore.UnitTests/Listener/Analysis/RaiseFindingToAnalysisIssueConverterTests.cs b/src/SLCore.UnitTests/Listener/Analysis/RaiseFindingToAnalysisIssueConverterTests.cs index c07dbde7ac..f94e426474 100644 --- a/src/SLCore.UnitTests/Listener/Analysis/RaiseFindingToAnalysisIssueConverterTests.cs +++ b/src/SLCore.UnitTests/Listener/Analysis/RaiseFindingToAnalysisIssueConverterTests.cs @@ -31,13 +31,14 @@ namespace SonarLint.VisualStudio.SLCore.UnitTests.Listener.Analysis; public class RaiseFindingToAnalysisIssueConverterTests { private RaiseFindingToAnalysisIssueConverter testSubject; + private readonly FileUri fileUri = new(@"C:\file"); [TestInitialize] public void TestInitialize() { testSubject = new RaiseFindingToAnalysisIssueConverter(); } - + [TestMethod] public void MefCtor_CheckIsExported() { @@ -59,34 +60,25 @@ public void GetAnalysisIssues_HasNoIssues_ReturnsEmpty() } [TestMethod] - public void GetAnalysisIssues_HasIssues_Converts() + public void GetAnalysisIssues_HasIssues_ConvertsCorrectly() { var dateTimeOffset = DateTimeOffset.Now; - - var issue1 = new RaisedIssueDto(Guid.NewGuid(), "serverKey1", "ruleKey1", "PrimaryMessage1", IssueSeverity.MAJOR, RuleType.CODE_SMELL, CleanCodeAttribute.EFFICIENT, new List(), dateTimeOffset, true, false, new TextRangeDto(1, 2, 3, 4), null, null, "context1"); - - var issue2impact1 = new ImpactDto(SoftwareQuality.SECURITY, ImpactSeverity.LOW); - var issue2impact2 = new ImpactDto(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.MEDIUM); - var issue2impact3 = new ImpactDto(SoftwareQuality.RELIABILITY, ImpactSeverity.HIGH); - var issue2impacts = new List() { issue2impact1, issue2impact2, issue2impact3 }; - - var issue2flow1Location1 = new IssueLocationDto(new TextRangeDto(11, 12, 13, 14), "Flow1Location1Message", new FileUri("C:\\flowFile1.cs")); - var issue2flow1Location2 = new IssueLocationDto(new TextRangeDto(21, 22, 23, 24), "Flow1Location2Message", new FileUri("C:\\flowFile1.cs")); - - var issue2flow1 = new IssueFlowDto(new List { issue2flow1Location1, issue2flow1Location2 }); - - var issue2flow2Location1 = new IssueLocationDto(new TextRangeDto(31, 32, 33, 34), "Flow2Location1Message", new FileUri("C:\\flowFile2.cs")); - var issue2flow2Location2 = new IssueLocationDto(new TextRangeDto(41, 42, 43, 44), "Flow2Location2Message", new FileUri("C:\\flowFile2.cs")); - - var issue2flow2 = new IssueFlowDto(new List { issue2flow2Location1, issue2flow2Location2 }); - - var issue2fix1fileedit1 = new FileEditDto(new FileUri("C:\\DifferentFile.cs"), new List()); - var issue2fix1 = new QuickFixDto(new List { issue2fix1fileedit1 }, "issue 2 fix 1"); - - var issue2fix2fileedit1Textedit1 = new TextEditDto(new TextRangeDto(51, 52, 53, 54), "new text"); - var issue2fix2fileedit1 = new FileEditDto(new FileUri("C:\\IssueFile.cs"), new List { issue2fix2fileedit1Textedit1 }); - var issue2fix2 = new QuickFixDto(new List { issue2fix2fileedit1 }, "issue 2 fix 2"); - + var issue1 = new RaisedIssueDto( + Guid.NewGuid(), + "serverKey1", + "ruleKey1", + "PrimaryMessage1", + IssueSeverity.MAJOR, + RuleType.CODE_SMELL, + CleanCodeAttribute.EFFICIENT, + [], + dateTimeOffset, + true, + false, + new TextRangeDto(1, 2, 3, 4), + null, + null, + "context1"); var issue2 = new RaisedIssueDto(Guid.NewGuid(), "serverKey2", "ruleKey2", @@ -94,101 +86,69 @@ public void GetAnalysisIssues_HasIssues_Converts() IssueSeverity.CRITICAL, RuleType.BUG, CleanCodeAttribute.LOGICAL, - issue2impacts, + IssueWithFlowsAndQuickFixesUseCase.Issue2Impacts, dateTimeOffset, true, false, new TextRangeDto(61, 62, 63, 64), - new List { issue2flow1, issue2flow2 }, - new List { issue2fix1, issue2fix2 }, + [IssueWithFlowsAndQuickFixesUseCase.Issue2Flow1, IssueWithFlowsAndQuickFixesUseCase.Issue2Flow2], + [IssueWithFlowsAndQuickFixesUseCase.Issue2Fix1, IssueWithFlowsAndQuickFixesUseCase.Issue2Fix2], "context2"); var result = testSubject.GetAnalysisIssues(new FileUri("C:\\IssueFile.cs"), new List { issue1, issue2 }).ToList(); - result.Should().NotBeNull(); - result.Should().HaveCount(2); - - result[0].RuleKey.Should().Be("ruleKey1"); - result[0].Severity.Should().Be(AnalysisIssueSeverity.Major); - result[0].Type.Should().Be(AnalysisIssueType.CodeSmell); - result[0].HighestSoftwareQualitySeverity.Should().BeNull(); - result[0].RuleDescriptionContextKey.Should().Be("context1"); - - result[0].PrimaryLocation.FilePath.Should().Be("C:\\IssueFile.cs"); - result[0].PrimaryLocation.Message.Should().Be("PrimaryMessage1"); - result[0].PrimaryLocation.TextRange.StartLine.Should().Be(1); - result[0].PrimaryLocation.TextRange.StartLineOffset.Should().Be(2); - result[0].PrimaryLocation.TextRange.EndLine.Should().Be(3); - result[0].PrimaryLocation.TextRange.EndLineOffset.Should().Be(4); - result[0].PrimaryLocation.TextRange.LineHash.Should().BeNull(); - - result[0].Flows.Should().BeEmpty(); - result[0].Fixes.Should().BeEmpty(); - - result[1].RuleKey.Should().Be("ruleKey2"); - result[1].Severity.Should().Be(AnalysisIssueSeverity.Critical); - result[1].Type.Should().Be(AnalysisIssueType.Bug); - result[1].HighestSoftwareQualitySeverity.Should().Be(SoftwareQualitySeverity.High); - result[1].RuleDescriptionContextKey.Should().Be("context2"); - - result[1].PrimaryLocation.FilePath.Should().Be("C:\\IssueFile.cs"); - result[1].PrimaryLocation.Message.Should().Be("PrimaryMessage2"); - result[1].PrimaryLocation.TextRange.StartLine.Should().Be(61); - result[1].PrimaryLocation.TextRange.StartLineOffset.Should().Be(62); - result[1].PrimaryLocation.TextRange.EndLine.Should().Be(63); - result[1].PrimaryLocation.TextRange.EndLineOffset.Should().Be(64); - result[1].PrimaryLocation.TextRange.LineHash.Should().BeNull(); - - result[1].Flows.Should().HaveCount(2); - result[1].Flows[0].Locations.Should().HaveCount(2); - - result[1].Flows[0].Locations[0].FilePath.Should().Be("C:\\flowFile1.cs"); - result[1].Flows[0].Locations[0].Message.Should().Be("Flow1Location1Message"); - result[1].Flows[0].Locations[0].TextRange.StartLine.Should().Be(11); - result[1].Flows[0].Locations[0].TextRange.StartLineOffset.Should().Be(12); - result[1].Flows[0].Locations[0].TextRange.EndLine.Should().Be(13); - result[1].Flows[0].Locations[0].TextRange.EndLineOffset.Should().Be(14); - result[1].Flows[0].Locations[0].TextRange.LineHash.Should().BeNull(); - result[1].Flows[0].Locations[1].FilePath.Should().Be("C:\\flowFile1.cs"); - result[1].Flows[0].Locations[1].Message.Should().Be("Flow1Location2Message"); - result[1].Flows[0].Locations[1].TextRange.StartLine.Should().Be(21); - result[1].Flows[0].Locations[1].TextRange.StartLineOffset.Should().Be(22); - result[1].Flows[0].Locations[1].TextRange.EndLine.Should().Be(23); - result[1].Flows[0].Locations[1].TextRange.EndLineOffset.Should().Be(24); - result[1].Flows[0].Locations[1].TextRange.LineHash.Should().BeNull(); - - result[1].Flows[1].Locations[0].FilePath.Should().Be("C:\\flowFile2.cs"); - result[1].Flows[1].Locations[0].Message.Should().Be("Flow2Location1Message"); - result[1].Flows[1].Locations[0].TextRange.StartLine.Should().Be(31); - result[1].Flows[1].Locations[0].TextRange.StartLineOffset.Should().Be(32); - result[1].Flows[1].Locations[0].TextRange.EndLine.Should().Be(33); - result[1].Flows[1].Locations[0].TextRange.EndLineOffset.Should().Be(34); - result[1].Flows[1].Locations[0].TextRange.LineHash.Should().BeNull(); - result[1].Flows[1].Locations[1].FilePath.Should().Be("C:\\flowFile2.cs"); - result[1].Flows[1].Locations[1].Message.Should().Be("Flow2Location2Message"); - result[1].Flows[1].Locations[1].TextRange.StartLine.Should().Be(41); - result[1].Flows[1].Locations[1].TextRange.StartLineOffset.Should().Be(42); - result[1].Flows[1].Locations[1].TextRange.EndLine.Should().Be(43); - result[1].Flows[1].Locations[1].TextRange.EndLineOffset.Should().Be(44); - result[1].Flows[1].Locations[1].TextRange.LineHash.Should().BeNull(); - - result[1].Fixes.Should().HaveCount(1); - result[1].Fixes[0].Message.Should().Be("issue 2 fix 2"); - result[1].Fixes[0].Edits.Should().HaveCount(1); - result[1].Fixes[0].Edits[0].RangeToReplace.StartLine.Should().Be(51); - result[1].Fixes[0].Edits[0].RangeToReplace.StartLineOffset.Should().Be(52); - result[1].Fixes[0].Edits[0].RangeToReplace.EndLine.Should().Be(53); - result[1].Fixes[0].Edits[0].RangeToReplace.EndLineOffset.Should().Be(54); - result[1].Fixes[0].Edits[0].RangeToReplace.LineHash.Should().BeNull(); + IssueWithFlowsAndQuickFixesUseCase.VerifyDtosConvertedCorrectly(result); + } + + [TestMethod] + public void GetAnalysisIssues_HasHotspot_ConvertsCorrectly() + { + var dateTimeOffset = DateTimeOffset.Now; + var issue1 = new RaisedHotspotDto(Guid.NewGuid(), + "serverKey1", + "ruleKey1", + "PrimaryMessage1", + IssueSeverity.MAJOR, + RuleType.CODE_SMELL, + CleanCodeAttribute.EFFICIENT, + [], + dateTimeOffset, + true, + false, + new TextRangeDto(1, 2, 3, 4), + null, + null, + "context1", + VulnerabilityProbability.HIGH, + HotspotStatus.FIXED); + var issue2 = new RaisedHotspotDto(Guid.NewGuid(), + "serverKey2", + "ruleKey2", + "PrimaryMessage2", + IssueSeverity.CRITICAL, + RuleType.BUG, + CleanCodeAttribute.LOGICAL, + IssueWithFlowsAndQuickFixesUseCase.Issue2Impacts, + dateTimeOffset, + true, + false, + new TextRangeDto(61, 62, 63, 64), + [IssueWithFlowsAndQuickFixesUseCase.Issue2Flow1, IssueWithFlowsAndQuickFixesUseCase.Issue2Flow2], + [IssueWithFlowsAndQuickFixesUseCase.Issue2Fix1, IssueWithFlowsAndQuickFixesUseCase.Issue2Fix2], + "context2", VulnerabilityProbability.HIGH, + HotspotStatus.FIXED); + + var result = testSubject.GetAnalysisIssues(new FileUri("C:\\IssueFile.cs"), new List { issue1, issue2 }).ToList(); + + IssueWithFlowsAndQuickFixesUseCase.VerifyDtosConvertedCorrectly(result); } [TestMethod] - public void GetAnalysisIssues_HasUnflattenedFlows_FlattensIntoSingleFlow() + public void GetAnalysisIssues_IssueHasUnflattenedFlows_FlattensIntoSingleFlow() { - var fileUri = new FileUri(@"C:\file"); - var analysisIssues = testSubject.GetAnalysisIssues(fileUri, new List + var analysisIssues = testSubject.GetAnalysisIssues(UnflattenedFlowsUseCase.FileUri, new List { - new(default, + new(Guid.Empty, default, default, default, @@ -203,28 +163,233 @@ public void GetAnalysisIssues_HasUnflattenedFlows_FlattensIntoSingleFlow() 2, 3, 4), - [ - new IssueFlowDto([new IssueLocationDto(new TextRangeDto(1, 1, 1, 1), "1", fileUri)]), - new IssueFlowDto([new IssueLocationDto(new TextRangeDto(2, 2, 2, 2), "2", fileUri)]), - new IssueFlowDto([new IssueLocationDto(new TextRangeDto(3, 3, 3, 3), "3", fileUri)]), - new IssueFlowDto([new IssueLocationDto(new TextRangeDto(4, 4, 4, 4), "4", fileUri)]), - new IssueFlowDto([new IssueLocationDto(new TextRangeDto(5, 5, 5, 5), "5", fileUri)]), - ], + UnflattenedFlowsUseCase.UnflattenedFlows, default, default) }); - analysisIssues.Single() - .Flows.Should() - .ContainSingle() - .Which.Locations.Should() - .HaveCount(5) - .And.BeEquivalentTo([ - new AnalysisIssueLocation("1", fileUri.LocalPath, new TextRange(1, 1, 1, 1, null)), - new AnalysisIssueLocation("2", fileUri.LocalPath, new TextRange(2, 2, 2, 2, null)), - new AnalysisIssueLocation("3", fileUri.LocalPath, new TextRange(3, 3, 3, 3, null)), - new AnalysisIssueLocation("4", fileUri.LocalPath, new TextRange(4, 4, 4, 4, null)), - new AnalysisIssueLocation("5", fileUri.LocalPath, new TextRange(5, 5, 5, 5, null)), - ]); + UnflattenedFlowsUseCase.VerifyFlattenedFlow(analysisIssues); + } + + [TestMethod] + public void GetAnalysisIssues_HotspotHasUnflattenedFlows_FlattensIntoSingleFlow() + { + var analysisIssues = testSubject.GetAnalysisIssues(UnflattenedFlowsUseCase.FileUri, new List + { + new(Guid.Empty, + default, + default, + default, + default, + default, + default, + default, + default, + default, + default, + new TextRangeDto(1, + 2, + 3, + 4), + UnflattenedFlowsUseCase.UnflattenedFlows, + default, + default, + VulnerabilityProbability.HIGH, + HotspotStatus.SAFE) + }); + + UnflattenedFlowsUseCase.VerifyFlattenedFlow(analysisIssues); + } + + [TestMethod] + [DataRow(VulnerabilityProbability.HIGH, HotspotPriority.High)] + [DataRow(VulnerabilityProbability.MEDIUM, HotspotPriority.Medium)] + [DataRow(VulnerabilityProbability.LOW, HotspotPriority.Low)] + public void GetAnalysisIssues_HotspotHasVulnerabilityProbability_AnalysisHotspotIssueIsCreatedWithCorrectHotspotPriority(VulnerabilityProbability vulnerabilityProbability, HotspotPriority expectedHotspotPriority) + { + var analysisIssues = testSubject.GetAnalysisIssues(fileUri, new List + { + new(Guid.Empty, + default, + default, + default, + default, + default, + default, + default, + default, + default, + default, + new TextRangeDto(1, + 2, + 3, + 4), + [], + default, + default, + vulnerabilityProbability, + HotspotStatus.SAFE) + }); + + analysisIssues.Single().Should().BeOfType().Which.HotspotPriority.Should().Be(expectedHotspotPriority); + } + + [TestMethod] + public void GetAnalysisIssues_HotspotHasNoVulnerabilityProbability_AnalysisHotspotIssueIsCreatedWithNoHotspotPriority() + { + var analysisIssues = testSubject.GetAnalysisIssues(fileUri, new List + { + new(Guid.Empty, + default, + default, + default, + default, + default, + default, + default, + default, + default, + default, + new TextRangeDto(1, + 2, + 3, + 4), + [], + default, + default, + null, + HotspotStatus.SAFE) + }); + + analysisIssues.Single().Should().BeOfType().Which.HotspotPriority.Should().BeNull(); + } + + private static class UnflattenedFlowsUseCase + { + internal static FileUri FileUri => new("C:\\IssueFile.cs"); + + internal static List UnflattenedFlows => [ + new IssueFlowDto([new IssueLocationDto(new TextRangeDto(1, 1, 1, 1), "1", FileUri)]), + new IssueFlowDto([new IssueLocationDto(new TextRangeDto(2, 2, 2, 2), "2", FileUri)]), + new IssueFlowDto([new IssueLocationDto(new TextRangeDto(3, 3, 3, 3), "3", FileUri)]), + new IssueFlowDto([new IssueLocationDto(new TextRangeDto(4, 4, 4, 4), "4", FileUri)]), + new IssueFlowDto([new IssueLocationDto(new TextRangeDto(5, 5, 5, 5), "5", FileUri)]), + ]; + + internal static void VerifyFlattenedFlow(IEnumerable analysisIssues) + { + analysisIssues.Single() + .Flows.Should() + .ContainSingle() + .Which.Locations.Should() + .HaveCount(5) + .And.BeEquivalentTo([ + new AnalysisIssueLocation("1", FileUri.LocalPath, new TextRange(1, 1, 1, 1, null)), + new AnalysisIssueLocation("2", FileUri.LocalPath, new TextRange(2, 2, 2, 2, null)), + new AnalysisIssueLocation("3", FileUri.LocalPath, new TextRange(3, 3, 3, 3, null)), + new AnalysisIssueLocation("4", FileUri.LocalPath, new TextRange(4, 4, 4, 4, null)), + new AnalysisIssueLocation("5", FileUri.LocalPath, new TextRange(5, 5, 5, 5, null)), + ]); + } + } + + private static class IssueWithFlowsAndQuickFixesUseCase + { + private static ImpactDto Issue2Impact1 => new(SoftwareQuality.SECURITY, ImpactSeverity.LOW); + private static ImpactDto Issue2Impact2 => new(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.MEDIUM); + private static ImpactDto Issue2Impact3 => new(SoftwareQuality.RELIABILITY, ImpactSeverity.HIGH); + internal static List Issue2Impacts => [Issue2Impact1, Issue2Impact2, Issue2Impact3]; + private static IssueLocationDto Issue2Flow1Location1 => new(new TextRangeDto(11, 12, 13, 14), "Flow1Location1Message", new FileUri("C:\\flowFile1.cs")); + private static IssueLocationDto Issue2Flow1Location2 => new(new TextRangeDto(21, 22, 23, 24), "Flow1Location2Message", new FileUri("C:\\flowFile1.cs")); + internal static IssueFlowDto Issue2Flow1 => new ([Issue2Flow1Location1, Issue2Flow1Location2]); + private static IssueLocationDto Issue2Flow2Location1 => new(new TextRangeDto(31, 32, 33, 34), "Flow2Location1Message", new FileUri("C:\\flowFile2.cs")); + private static IssueLocationDto Issue2Flow2Location2 => new(new TextRangeDto(41, 42, 43, 44), "Flow2Location2Message", new FileUri("C:\\flowFile2.cs")); + internal static IssueFlowDto Issue2Flow2 => new([Issue2Flow2Location1, Issue2Flow2Location2]); + private static FileEditDto Issue2Fix1FileEdit1 => new(new FileUri("C:\\DifferentFile.cs"), []); + internal static QuickFixDto Issue2Fix1 => new([Issue2Fix1FileEdit1], "issue 2 fix 1"); + private static TextEditDto Issue2Fix2FileEdit1Textedit1 => new(new TextRangeDto(51, 52, 53, 54), "new text"); + private static FileEditDto Issue2Fix2FileEdit1 => new(new FileUri("C:\\IssueFile.cs"), [Issue2Fix2FileEdit1Textedit1]); + internal static QuickFixDto Issue2Fix2 => new([Issue2Fix2FileEdit1], "issue 2 fix 2"); + + + internal static void VerifyDtosConvertedCorrectly(List result) + { + result.Should().NotBeNull(); + result.Should().HaveCount(2); + + result[0].RuleKey.Should().Be("ruleKey1"); + result[0].Severity.Should().Be(AnalysisIssueSeverity.Major); + result[0].Type.Should().Be(AnalysisIssueType.CodeSmell); + result[0].HighestSoftwareQualitySeverity.Should().BeNull(); + result[0].RuleDescriptionContextKey.Should().Be("context1"); + + result[0].PrimaryLocation.FilePath.Should().Be("C:\\IssueFile.cs"); + result[0].PrimaryLocation.Message.Should().Be("PrimaryMessage1"); + result[0].PrimaryLocation.TextRange.StartLine.Should().Be(1); + result[0].PrimaryLocation.TextRange.StartLineOffset.Should().Be(2); + result[0].PrimaryLocation.TextRange.EndLine.Should().Be(3); + result[0].PrimaryLocation.TextRange.EndLineOffset.Should().Be(4); + result[0].PrimaryLocation.TextRange.LineHash.Should().BeNull(); + + result[0].Flows.Should().BeEmpty(); + result[0].Fixes.Should().BeEmpty(); + + result[1].RuleKey.Should().Be("ruleKey2"); + result[1].Severity.Should().Be(AnalysisIssueSeverity.Critical); + result[1].Type.Should().Be(AnalysisIssueType.Bug); + result[1].HighestSoftwareQualitySeverity.Should().Be(SoftwareQualitySeverity.High); + result[1].RuleDescriptionContextKey.Should().Be("context2"); + + result[1].PrimaryLocation.FilePath.Should().Be("C:\\IssueFile.cs"); + result[1].PrimaryLocation.Message.Should().Be("PrimaryMessage2"); + result[1].PrimaryLocation.TextRange.StartLine.Should().Be(61); + result[1].PrimaryLocation.TextRange.StartLineOffset.Should().Be(62); + result[1].PrimaryLocation.TextRange.EndLine.Should().Be(63); + result[1].PrimaryLocation.TextRange.EndLineOffset.Should().Be(64); + result[1].PrimaryLocation.TextRange.LineHash.Should().BeNull(); + + result[1].Flows.Should().HaveCount(2); + result[1].Flows[0].Locations.Should().HaveCount(2); + + result[1].Flows[0].Locations[0].FilePath.Should().Be("C:\\flowFile1.cs"); + result[1].Flows[0].Locations[0].Message.Should().Be("Flow1Location1Message"); + result[1].Flows[0].Locations[0].TextRange.StartLine.Should().Be(11); + result[1].Flows[0].Locations[0].TextRange.StartLineOffset.Should().Be(12); + result[1].Flows[0].Locations[0].TextRange.EndLine.Should().Be(13); + result[1].Flows[0].Locations[0].TextRange.EndLineOffset.Should().Be(14); + result[1].Flows[0].Locations[0].TextRange.LineHash.Should().BeNull(); + result[1].Flows[0].Locations[1].FilePath.Should().Be("C:\\flowFile1.cs"); + result[1].Flows[0].Locations[1].Message.Should().Be("Flow1Location2Message"); + result[1].Flows[0].Locations[1].TextRange.StartLine.Should().Be(21); + result[1].Flows[0].Locations[1].TextRange.StartLineOffset.Should().Be(22); + result[1].Flows[0].Locations[1].TextRange.EndLine.Should().Be(23); + result[1].Flows[0].Locations[1].TextRange.EndLineOffset.Should().Be(24); + result[1].Flows[0].Locations[1].TextRange.LineHash.Should().BeNull(); + + result[1].Flows[1].Locations[0].FilePath.Should().Be("C:\\flowFile2.cs"); + result[1].Flows[1].Locations[0].Message.Should().Be("Flow2Location1Message"); + result[1].Flows[1].Locations[0].TextRange.StartLine.Should().Be(31); + result[1].Flows[1].Locations[0].TextRange.StartLineOffset.Should().Be(32); + result[1].Flows[1].Locations[0].TextRange.EndLine.Should().Be(33); + result[1].Flows[1].Locations[0].TextRange.EndLineOffset.Should().Be(34); + result[1].Flows[1].Locations[0].TextRange.LineHash.Should().BeNull(); + result[1].Flows[1].Locations[1].FilePath.Should().Be("C:\\flowFile2.cs"); + result[1].Flows[1].Locations[1].Message.Should().Be("Flow2Location2Message"); + result[1].Flows[1].Locations[1].TextRange.StartLine.Should().Be(41); + result[1].Flows[1].Locations[1].TextRange.StartLineOffset.Should().Be(42); + result[1].Flows[1].Locations[1].TextRange.EndLine.Should().Be(43); + result[1].Flows[1].Locations[1].TextRange.EndLineOffset.Should().Be(44); + result[1].Flows[1].Locations[1].TextRange.LineHash.Should().BeNull(); + + result[1].Fixes.Should().HaveCount(1); + result[1].Fixes[0].Message.Should().Be("issue 2 fix 2"); + result[1].Fixes[0].Edits.Should().HaveCount(1); + result[1].Fixes[0].Edits[0].RangeToReplace.StartLine.Should().Be(51); + result[1].Fixes[0].Edits[0].RangeToReplace.StartLineOffset.Should().Be(52); + result[1].Fixes[0].Edits[0].RangeToReplace.EndLine.Should().Be(53); + result[1].Fixes[0].Edits[0].RangeToReplace.EndLineOffset.Should().Be(54); + result[1].Fixes[0].Edits[0].RangeToReplace.LineHash.Should().BeNull(); + } + } } diff --git a/src/SLCore/Common/Helpers/ModelConversionExtensions.cs b/src/SLCore/Common/Helpers/ModelConversionExtensions.cs index 1466154970..050bb65038 100644 --- a/src/SLCore/Common/Helpers/ModelConversionExtensions.cs +++ b/src/SLCore/Common/Helpers/ModelConversionExtensions.cs @@ -60,5 +60,17 @@ public static SoftwareQualitySeverity ToSoftwareQualitySeverity(this ImpactSever _ => throw new ArgumentOutOfRangeException(nameof(impactSeverity), impactSeverity, SLCoreStrings.ModelExtensions_UnexpectedValue), }; } + + public static HotspotPriority? GetHotspotPriority(this VulnerabilityProbability? vulnerabilityProbability) + { + return vulnerabilityProbability switch + { + null => null, + VulnerabilityProbability.HIGH => HotspotPriority.High, + VulnerabilityProbability.MEDIUM => HotspotPriority.Medium, + VulnerabilityProbability.LOW => HotspotPriority.Low, + _ => throw new ArgumentOutOfRangeException(nameof(vulnerabilityProbability), vulnerabilityProbability, SLCoreStrings.ModelExtensions_UnexpectedValue), + }; + } } } diff --git a/src/SLCore/Listener/Analysis/RaiseFindingToAnalysisIssueConverter.cs b/src/SLCore/Listener/Analysis/RaiseFindingToAnalysisIssueConverter.cs index 51df605e9a..0349793731 100644 --- a/src/SLCore/Listener/Analysis/RaiseFindingToAnalysisIssueConverter.cs +++ b/src/SLCore/Listener/Analysis/RaiseFindingToAnalysisIssueConverter.cs @@ -32,16 +32,43 @@ public class RaiseFindingToAnalysisIssueConverter : IRaiseFindingToAnalysisIssue { public IEnumerable GetAnalysisIssues(FileUri fileUri, IEnumerable raisedFindings) where T : RaisedFindingDto => raisedFindings - .Select(item => new AnalysisIssue(item.ruleKey, - item.severity.ToAnalysisIssueSeverity(), - item.type.ToAnalysisIssueType(), - GetHighestSoftwareQualitySeverity(item.impacts), - GetAnalysisIssueLocation(fileUri.LocalPath, item.primaryMessage, item.textRange), - GetFlows(item.flows), - item.quickFixes?.Select(qf => GetQuickFix(fileUri, qf)).Where(qf => qf is not null).ToList(), - item.ruleDescriptionContextKey)) + .Select(item => CreateAnalysisIssue(fileUri, item)) .ToList(); + private static AnalysisIssue CreateAnalysisIssue(FileUri fileUri, T item) where T : RaisedFindingDto + { + var itemRuleKey = item.ruleKey; + var analysisIssueSeverity = item.severity.ToAnalysisIssueSeverity(); + var analysisIssueType = item.type.ToAnalysisIssueType(); + var highestSoftwareQualitySeverity = GetHighestSoftwareQualitySeverity(item.impacts); + var analysisIssueLocation = GetAnalysisIssueLocation(fileUri.LocalPath, item.primaryMessage, item.textRange); + var analysisIssueFlows = GetFlows(item.flows); + var readOnlyList = item.quickFixes?.Select(qf => GetQuickFix(fileUri, qf)).Where(qf => qf is not null).ToList(); + var itemRuleDescriptionContextKey = item.ruleDescriptionContextKey; + + if (item is RaisedHotspotDto raisedHotspotDto) + { + return new AnalysisHotspotIssue(itemRuleKey, + analysisIssueSeverity, + analysisIssueType, + highestSoftwareQualitySeverity, + analysisIssueLocation, + analysisIssueFlows, + readOnlyList, + itemRuleDescriptionContextKey, + raisedHotspotDto.vulnerabilityProbability.GetHotspotPriority()); + } + + return new AnalysisIssue(itemRuleKey, + analysisIssueSeverity, + analysisIssueType, + highestSoftwareQualitySeverity, + analysisIssueLocation, + analysisIssueFlows, + readOnlyList, + itemRuleDescriptionContextKey); + } + private static SoftwareQualitySeverity? GetHighestSoftwareQualitySeverity(List impacts) => impacts is not null && impacts.Any() ? impacts.Max(i => i.impactSeverity).ToSoftwareQualitySeverity() @@ -63,7 +90,7 @@ private static IAnalysisIssueFlow[] GetFlows(List issueFlows) return []; } - if (issueFlows.All(x => x.locations?.Count == 1)) + if (issueFlows.TrueForAll(x => x.locations?.Count == 1)) { return [GetAnalysisIssueFlow(issueFlows.SelectMany(x => x.locations))]; }