From 902a3a3108a91d756f525ba2b3a0a6d15510eb21 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Tue, 12 Nov 2024 13:09:07 +0100 Subject: [PATCH 01/14] SLVS-1598 Add Id to the IFilterableIssue interface --- src/ConnectedMode.UnitTests/TestFilterableIssue.cs | 1 + src/Core/Suppressions/FilterableRoslynIssue.cs | 4 ++++ src/Core/Suppressions/IFilterableIssue.cs | 5 +++++ .../Models/AnalysisIssueVisualizationTests.cs | 3 +++ src/IssueViz/Models/AnalysisIssueVisualization.cs | 1 + 5 files changed, 14 insertions(+) diff --git a/src/ConnectedMode.UnitTests/TestFilterableIssue.cs b/src/ConnectedMode.UnitTests/TestFilterableIssue.cs index 845a0531ed..49a776772a 100644 --- a/src/ConnectedMode.UnitTests/TestFilterableIssue.cs +++ b/src/ConnectedMode.UnitTests/TestFilterableIssue.cs @@ -25,6 +25,7 @@ namespace SonarLint.VisualStudio.ConnectedMode.UnitTests; internal class TestFilterableIssue : IFilterableIssue { + public Guid? IssueId { get; set; } public string RuleId { get; set; } public string LineHash { get; set; } public int? StartLine { get; set; } diff --git a/src/Core/Suppressions/FilterableRoslynIssue.cs b/src/Core/Suppressions/FilterableRoslynIssue.cs index e72f86399c..d6ab1f7a88 100644 --- a/src/Core/Suppressions/FilterableRoslynIssue.cs +++ b/src/Core/Suppressions/FilterableRoslynIssue.cs @@ -40,6 +40,10 @@ public FilterableRoslynIssue(string ruleId, string filePath, int startLine, int RoslynStartColumn = startColumn; } + /// + /// Always null, as this Id is specific to SlCore + /// + public Guid? IssueId { get; } public string RuleId { get; } public string FilePath { get; } public int? StartLine => RoslynStartLine; diff --git a/src/Core/Suppressions/IFilterableIssue.cs b/src/Core/Suppressions/IFilterableIssue.cs index 34ef449f47..bb5e5256ef 100644 --- a/src/Core/Suppressions/IFilterableIssue.cs +++ b/src/Core/Suppressions/IFilterableIssue.cs @@ -26,6 +26,11 @@ namespace SonarLint.VisualStudio.Core.Suppressions /// public interface IFilterableIssue { + /// + /// The id of the issue that comes from SlCore + /// Nullable due to the fact that some issues do not come from SlCore (e.g. Roslyn) + /// + Guid? IssueId { get; } string RuleId { get; } string FilePath { get; } string LineHash { get; } diff --git a/src/IssueViz.UnitTests/Models/AnalysisIssueVisualizationTests.cs b/src/IssueViz.UnitTests/Models/AnalysisIssueVisualizationTests.cs index c380357fea..93dab60f90 100644 --- a/src/IssueViz.UnitTests/Models/AnalysisIssueVisualizationTests.cs +++ b/src/IssueViz.UnitTests/Models/AnalysisIssueVisualizationTests.cs @@ -211,7 +211,9 @@ public void SetIsSuppressed_HasSubscribers_VerifyRaised() [TestMethod] public void IsFilterable() { + var id = Guid.NewGuid(); var issueMock = new Mock(); + 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); @@ -223,6 +225,7 @@ public void IsFilterable() var filterable = (IFilterableIssue)testSubject; + 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); diff --git a/src/IssueViz/Models/AnalysisIssueVisualization.cs b/src/IssueViz/Models/AnalysisIssueVisualization.cs index 15cdfd9dae..7facf0c2e7 100644 --- a/src/IssueViz/Models/AnalysisIssueVisualization.cs +++ b/src/IssueViz/Models/AnalysisIssueVisualization.cs @@ -107,6 +107,7 @@ protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyN PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } + public Guid? IssueId => Issue.Id; string IFilterableIssue.RuleId => Issue.RuleKey; string IFilterableIssue.FilePath => CurrentFilePath; From 970317383087cddfcd2c117369e77971b5b3ba39 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Tue, 12 Nov 2024 16:16:10 +0100 Subject: [PATCH 02/14] SLVS-1598 Extend ErrorListHelper to provide method that gets a IFilterableIssue from the corresponding error row from VS error list --- .../ErrorListHelperTests.cs | 38 +++++++++++++++++++ src/Infrastructure.VS/ErrorListHelper.cs | 21 ++++++++-- src/Infrastructure.VS/IErrorListHelper.cs | 7 ++++ 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/Infrastructure.VS.UnitTests/ErrorListHelperTests.cs b/src/Infrastructure.VS.UnitTests/ErrorListHelperTests.cs index 83845ae333..ec70d46d49 100644 --- a/src/Infrastructure.VS.UnitTests/ErrorListHelperTests.cs +++ b/src/Infrastructure.VS.UnitTests/ErrorListHelperTests.cs @@ -446,6 +446,44 @@ public void TryGetRuleIdAndSuppressionStateFromSelectedRow_NoSuppressionState_Re isSuppressed.Should().Be(expectedSuppression); } + [TestMethod] + public void TryGetFilterableIssue_SonarIssue_IssueReturned() + { + var issueMock = Mock.Of(); + var issueHandle = CreateIssueHandle(111, new Dictionary + { + { StandardTableKeyNames.BuildTool, "SonarLint" }, + { StandardTableKeyNames.ErrorCode, "javascript:S333"}, + { SonarLintTableControlConstants.IssueVizColumnName, issueMock } + }); + var errorList = CreateErrorList(issueHandle); + var serviceProvider = CreateServiceOperation(errorList); + var testSubject = new ErrorListHelper(serviceProvider); + + bool result = testSubject.TryGetFilterableIssue(issueHandle, out var issue); + + result.Should().BeTrue(); + issue.Should().BeSameAs(issueMock); + } + + [TestMethod] + public void TryGetFilterableIssue_NoAnalysisIssue_IssueNotReturned() + { + var issueHandle = CreateIssueHandle(111, new Dictionary + { + { StandardTableKeyNames.BuildTool, "SonarLint" }, + { 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 _); + + result.Should().BeFalse(); + } + private IVsUIServiceOperation CreateServiceOperation(IErrorList svcToPassToCallback) { var serviceOp = new Mock(); diff --git a/src/Infrastructure.VS/ErrorListHelper.cs b/src/Infrastructure.VS/ErrorListHelper.cs index 463d14a3ba..78554a53fe 100644 --- a/src/Infrastructure.VS/ErrorListHelper.cs +++ b/src/Infrastructure.VS/ErrorListHelper.cs @@ -18,7 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; using System.ComponentModel.Composition; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; @@ -97,6 +96,18 @@ public bool TryGetIssueFromSelectedRow(out IFilterableIssue issue) return result; } + public bool TryGetFilterableIssue(ITableEntryHandle handle, out IFilterableIssue issue) + { + IFilterableIssue issueOut = null; + var result = vSServiceOperation.Execute( + _ => handle.TryGetSnapshot(out var snapshot, out var index) + && TryGetValue(snapshot, index, SonarLintTableControlConstants.IssueVizColumnName, out issueOut)); + + issue = issueOut; + + return result; + } + public bool TryGetRoslynIssueFromSelectedRow(out IFilterableRoslynIssue filterableRoslynIssue) { IFilterableRoslynIssue outIssue = null; @@ -155,7 +166,7 @@ private static string FindErrorCodeForEntry(ITableEntriesSnapshot snapshot, int { return $"{SonarRuleRepoKeys.CSharpRules}:{errorCode}"; } - + if (helpLink.Contains("rules.sonarsource.com/vbnet/")) { return $"{SonarRuleRepoKeys.VBNetRules}:{errorCode}"; @@ -197,7 +208,11 @@ private static bool TryGetSelectedTableEntry(IErrorList errorList, out ITableEnt return true; } - private static bool TryGetValue(ITableEntriesSnapshot snapshot, int index, string columnName, out T value) + private static bool TryGetValue( + ITableEntriesSnapshot snapshot, + int index, + string columnName, + out T value) { value = default; diff --git a/src/Infrastructure.VS/IErrorListHelper.cs b/src/Infrastructure.VS/IErrorListHelper.cs index 5661e5eb00..1bf57fdb64 100644 --- a/src/Infrastructure.VS/IErrorListHelper.cs +++ b/src/Infrastructure.VS/IErrorListHelper.cs @@ -51,6 +51,13 @@ public interface IErrorListHelper /// True if issue is present in the selected row, False if not present or multiple rows selected bool TryGetIssueFromSelectedRow(out IFilterableIssue issue); + /// + /// Extracts, if present, from the hidden column + /// The method will only return a rule key if the row represents a Sonar analysis issue for any supported language (including Roslyn languages i.e. C# and VB.NET) + /// + /// True if issue is present in the provided row + bool TryGetFilterableIssue(ITableEntryHandle handle, out IFilterableIssue issue); + /// /// Extracts from error code, line number and file path. Does not calculate line hash. /// From 29ff41d5b9758f2c6fcb8ea35edcc5e41e28e0ee Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Tue, 12 Nov 2024 16:49:04 +0100 Subject: [PATCH 03/14] SLVS-1598 Introduce common interface for DTOs --- .../GetEffectiveIssueDetailsResponseTests.cs | 2 +- .../Issue/Models/EffectiveIssueDetailsDto.cs | 4 +-- .../Rules/Models/EffectiveRuleDetailsDto.cs | 5 +-- .../Service/Rules/Models/IRuleDetails.cs | 35 +++++++++++++++++++ 4 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 src/SLCore/Service/Rules/Models/IRuleDetails.cs diff --git a/src/SLCore.UnitTests/Service/Issue/GetEffectiveIssueDetailsResponseTests.cs b/src/SLCore.UnitTests/Service/Issue/GetEffectiveIssueDetailsResponseTests.cs index fa33a74ce1..a8d77dfe85 100644 --- a/src/SLCore.UnitTests/Service/Issue/GetEffectiveIssueDetailsResponseTests.cs +++ b/src/SLCore.UnitTests/Service/Issue/GetEffectiveIssueDetailsResponseTests.cs @@ -34,7 +34,7 @@ public void Deserialized_AsExpected() { var expected = new GetEffectiveIssueDetailsResponse( details: new EffectiveIssueDetailsDto( - ruleKey: "S3776", + key: "S3776", name: "Cognitive Complexity of methods should not be too high", language: Language.CS, vulnerabilityProbability: VulnerabilityProbability.HIGH, diff --git a/src/SLCore/Service/Issue/Models/EffectiveIssueDetailsDto.cs b/src/SLCore/Service/Issue/Models/EffectiveIssueDetailsDto.cs index 9bce238de9..e86d9697e0 100644 --- a/src/SLCore/Service/Issue/Models/EffectiveIssueDetailsDto.cs +++ b/src/SLCore/Service/Issue/Models/EffectiveIssueDetailsDto.cs @@ -26,11 +26,11 @@ namespace SonarLint.VisualStudio.SLCore.Service.Issue.Models; public record EffectiveIssueDetailsDto( - string ruleKey, + [JsonProperty("ruleKey")] string key, string name, Language language, VulnerabilityProbability? vulnerabilityProbability, [JsonConverter(typeof(EitherJsonConverter))] Either description, [JsonProperty("params")] List parameters, [JsonConverter(typeof(EitherJsonConverter))] Either severityDetails, - string ruleDescriptionContextKey); + string ruleDescriptionContextKey) : IRuleDetails; diff --git a/src/SLCore/Service/Rules/Models/EffectiveRuleDetailsDto.cs b/src/SLCore/Service/Rules/Models/EffectiveRuleDetailsDto.cs index 25455f34bb..45b4798ecf 100644 --- a/src/SLCore/Service/Rules/Models/EffectiveRuleDetailsDto.cs +++ b/src/SLCore/Service/Rules/Models/EffectiveRuleDetailsDto.cs @@ -18,9 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System.Collections.Generic; using Newtonsoft.Json; -using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.SLCore.Common.Models; using SonarLint.VisualStudio.SLCore.Protocol; using Language = SonarLint.VisualStudio.SLCore.Common.Models.Language; @@ -36,8 +34,7 @@ public record EffectiveRuleDetailsDto( VulnerabilityProbability? vulnerabilityProbability, [property: JsonConverter(typeof(EitherJsonConverter))] Either description, - List @params); + [JsonProperty("params")] List parameters) : IRuleDetails; public record StandardModeDetails(IssueSeverity severity, RuleType type); - public record MQRModeDetails(CleanCodeAttribute cleanCodeAttribute, List impacts); diff --git a/src/SLCore/Service/Rules/Models/IRuleDetails.cs b/src/SLCore/Service/Rules/Models/IRuleDetails.cs new file mode 100644 index 0000000000..1f41d4a522 --- /dev/null +++ b/src/SLCore/Service/Rules/Models/IRuleDetails.cs @@ -0,0 +1,35 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using SonarLint.VisualStudio.SLCore.Common.Models; +using SonarLint.VisualStudio.SLCore.Protocol; + +namespace SonarLint.VisualStudio.SLCore.Service.Rules.Models; + +public interface IRuleDetails +{ + string key { get; } + string name { get; } + Language language { get; } + Either severityDetails { get; } + VulnerabilityProbability? vulnerabilityProbability { get; } + Either description { get; } + List parameters { get; } +} From 1f9170c040146608957d3c23132a39de65ef13d1 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Wed, 13 Nov 2024 08:32:00 +0100 Subject: [PATCH 04/14] SLVS-1598 Extend RuleMetadataProvider to allow getting a rule info from an issue --- .../Rule/SLCoreRuleMetaDataProviderTests.cs | 358 ++++++++++++++++++ src/Education/Rule/IRuleMetaDataProvider.cs | 10 +- .../Rule/SLCoreRuleMetaDataProvider.cs | 36 +- 3 files changed, 397 insertions(+), 7 deletions(-) diff --git a/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs b/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs index 1309d6d22f..6635249693 100644 --- a/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs +++ b/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs @@ -20,6 +20,7 @@ using Moq; using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.Suppressions; using SonarLint.VisualStudio.Education.Rule; using SonarLint.VisualStudio.SLCore.Common.Models; using SonarLint.VisualStudio.SLCore.Core; @@ -35,12 +36,16 @@ using RuleCleanCodeAttribute = SonarLint.VisualStudio.Core.Analysis.CleanCodeAttribute; using RuleSoftwareQuality = SonarLint.VisualStudio.Core.Analysis.SoftwareQuality; using RuleSoftwareQualitySeverity = SonarLint.VisualStudio.Core.Analysis.SoftwareQualitySeverity; +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"); + [TestMethod] public void MefCtor_CheckIsExported() => MefTestHelpers.CheckTypeCanBeImported( @@ -360,6 +365,329 @@ public void GetRuleInfoAsync_ServiceThrows_ReturnsNullAndLogs() logger.AssertPartialOutputStringExists("my message"); } + [DataTestMethod] + [DataRow(IssueSeverity.INFO, RuleIssueSeverity.Info)] + [DataRow(IssueSeverity.MAJOR, RuleIssueSeverity.Major)] + [DataRow(IssueSeverity.BLOCKER, RuleIssueSeverity.Blocker)] + [DataRow(IssueSeverity.CRITICAL, RuleIssueSeverity.Critical)] + [DataRow(IssueSeverity.MINOR, RuleIssueSeverity.Minor)] + public async Task GetEffectiveIssueDetailsAsync_CorrectlyConvertsSeverity(IssueSeverity slCore, RuleIssueSeverity expected) + { + var issueId = Guid.NewGuid(); + var testSubject = + CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _); + SetUpIssueServiceProvider(serviceProviderMock, out var issuesServiceMock); + SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); + SetupIssuesService(issuesServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new StandardModeDetails(slCore, default))); + + var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(issueId); + + ruleInfo.Severity.Should().Be(expected); + } + + [DataTestMethod] + [DataRow(RuleType.CODE_SMELL, RuleIssueType.CodeSmell)] + [DataRow(RuleType.VULNERABILITY, RuleIssueType.Vulnerability)] + [DataRow(RuleType.BUG, RuleIssueType.Bug)] + [DataRow(RuleType.SECURITY_HOTSPOT, RuleIssueType.Hotspot)] + public async Task GetEffectiveIssueDetailsAsync_CorrectlyConvertsType(RuleType slCore, RuleIssueType expected) + { + var issueId = Guid.NewGuid(); + var testSubject = + CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _); + SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock); + SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); + SetupIssuesService(issueServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new StandardModeDetails(default, slCore))); + + var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(issueId); + + ruleInfo.IssueType.Should().Be(expected); + } + + [DataTestMethod] + [DataRow(CleanCodeAttribute.CONVENTIONAL, RuleCleanCodeAttribute.Conventional)] + [DataRow(CleanCodeAttribute.FORMATTED, RuleCleanCodeAttribute.Formatted)] + [DataRow(CleanCodeAttribute.IDENTIFIABLE, RuleCleanCodeAttribute.Identifiable)] + [DataRow(CleanCodeAttribute.CLEAR, RuleCleanCodeAttribute.Clear)] + [DataRow(CleanCodeAttribute.COMPLETE, RuleCleanCodeAttribute.Complete)] + [DataRow(CleanCodeAttribute.EFFICIENT, RuleCleanCodeAttribute.Efficient)] + [DataRow(CleanCodeAttribute.LOGICAL, RuleCleanCodeAttribute.Logical)] + [DataRow(CleanCodeAttribute.DISTINCT, RuleCleanCodeAttribute.Distinct)] + [DataRow(CleanCodeAttribute.FOCUSED, RuleCleanCodeAttribute.Focused)] + [DataRow(CleanCodeAttribute.MODULAR, RuleCleanCodeAttribute.Modular)] + [DataRow(CleanCodeAttribute.TESTED, RuleCleanCodeAttribute.Tested)] + [DataRow(CleanCodeAttribute.LAWFUL, RuleCleanCodeAttribute.Lawful)] + [DataRow(CleanCodeAttribute.RESPECTFUL, RuleCleanCodeAttribute.Respectful)] + [DataRow(CleanCodeAttribute.TRUSTWORTHY, RuleCleanCodeAttribute.Trustworthy)] + public async Task GetEffectiveIssueDetailsAsync_CorrectlyConvertsCleanCodeAttribute(CleanCodeAttribute slCore, RuleCleanCodeAttribute expected) + { + var issueId = Guid.NewGuid(); + var testSubject = + CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _); + SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock); + SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); + SetupIssuesService(issueServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new MQRModeDetails(slCore, default))); + + var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(issueId); + + ruleInfo.CleanCodeAttribute.Should().Be(expected); + } + + [TestMethod] + public async Task GetEffectiveIssueDetailsAsync_CorrectlyConvertsImpacts() + { + var issueId = Guid.NewGuid(); + var testSubject = + CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _); + SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock); + SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); + SetupIssuesService(issueServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new MQRModeDetails(default, [ + new ImpactDto(SoftwareQuality.SECURITY, ImpactSeverity.HIGH), + new ImpactDto(SoftwareQuality.RELIABILITY, ImpactSeverity.LOW), + new ImpactDto(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.MEDIUM) + ]))); + + var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(issueId); + + ruleInfo.DefaultImpacts.Should().BeEquivalentTo(new Dictionary + { + { RuleSoftwareQuality.Security, RuleSoftwareQualitySeverity.High }, + { RuleSoftwareQuality.Reliability, RuleSoftwareQualitySeverity.Low }, + { RuleSoftwareQuality.Maintainability, RuleSoftwareQualitySeverity.Medium } + }); + } + + [TestMethod] + public async Task GetEffectiveIssueDetailsAsync_Standard_SimpleRuleDescription() + { + var issueId = Guid.NewGuid(); + var testSubject = + CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _); + SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock); + SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); + SetupIssuesService(issueServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new StandardModeDetails(IssueSeverity.CRITICAL, RuleType.VULNERABILITY), + Either.CreateLeft( + new RuleMonolithicDescriptionDto("content")))); + + var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(issueId); + + ruleInfo.Should().BeEquivalentTo(new RuleInfo(null, + "content", + null, + RuleIssueSeverity.Critical, + RuleIssueType.Vulnerability, + null, + null, + null)); + } + + [TestMethod] + public async Task GetEffectiveIssueDetailsAsync_MQR_SimpleRuleDescription() + { + var issueId = Guid.NewGuid(); + var testSubject = + CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _); + SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock); + SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); + SetupIssuesService(issueServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new MQRModeDetails(CleanCodeAttribute.MODULAR, default), Either.CreateLeft( + new RuleMonolithicDescriptionDto("content")))); + + var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(issueId); + + ruleInfo.Should().BeEquivalentTo(new RuleInfo(null, + "content", + null, + null, + null, + null, + RuleCleanCodeAttribute.Modular, + null)); + } + + [TestMethod] + public async Task GetEffectiveIssueDetailsAsync_Standard_RichRuleDescription() + { + var issueId = Guid.NewGuid(); + var testSubject = + CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out var logger); + SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock); + SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); + var ruleSplitDescriptionDto = new RuleSplitDescriptionDto("intro", new List()); + SetupIssuesService(issueServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new StandardModeDetails(IssueSeverity.MINOR, RuleType.BUG), + Either.CreateRight(ruleSplitDescriptionDto))); + + var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(issueId); + + ruleInfo.Should().BeEquivalentTo(new RuleInfo(null, + null, + null, + RuleIssueSeverity.Minor, + RuleIssueType.Bug, + ruleSplitDescriptionDto, + null, + null)); + logger.AssertNoOutputMessages(); + } + + [TestMethod] + public async Task GetEffectiveIssueDetailsAsync_MQR_RichRuleDescription() + { + var issueId = Guid.NewGuid(); + var testSubject = + CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out var logger); + SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock); + SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); + var ruleSplitDescriptionDto = new RuleSplitDescriptionDto("intro", new List()); + SetupIssuesService(issueServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new MQRModeDetails(CleanCodeAttribute.RESPECTFUL, default), + Either.CreateRight(ruleSplitDescriptionDto))); + + var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(issueId); + + ruleInfo.Should().BeEquivalentTo(new RuleInfo(null, + null, + null, + null, + null, + ruleSplitDescriptionDto, + RuleCleanCodeAttribute.Respectful, + null)); + logger.AssertNoOutputMessages(); + } + + [TestMethod] + public async Task GetEffectiveIssueDetailsAsync_NoActiveScope_ReturnsNull() + { + var testSubject = + CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out var logger); + SetUpIssueServiceProvider(serviceProviderMock, out _); + SetUpConfigScopeTracker(configScopeTrackerMock, null); + var issueId = Guid.NewGuid(); + + var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(issueId); + + ruleInfo.Should().BeNull(); + logger.AssertNoOutputMessages(); + } + + [TestMethod] + public async Task GetEffectiveIssueDetailsAsync_ServiceUnavailable_ReturnsNull() + { + var testSubject = CreateTestSubject(out _, out var configScopeTrackerMock, out var logger); + SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("id")); + + var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(Guid.NewGuid()); + + ruleInfo.Should().BeNull(); + logger.AssertNoOutputMessages(); + } + + [TestMethod] + public void GetEffectiveIssueDetailsAsync_ServiceThrows_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())) + .ThrowsAsync(new Exception("my message")); + + var act = () => testSubject.GetEffectiveIssueDetailsAsync(Guid.NewGuid()); + + act.Should().NotThrow(); + logger.AssertPartialOutputStringExists("my message"); + } + + [TestMethod] + public async Task GetRuleInfoAsync_FilterableIssueNull_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); + + rulesServiceMock.Verify(x => x.GetEffectiveRuleDetailsAsync(It.Is(p => p.ruleKey == _compositeRuleId.ToString())), Times.Once); + issueServiceMock.Verify(x => x.GetEffectiveIssueDetailsAsync(It.IsAny()), Times.Never); + } + + [TestMethod] + public async Task GetRuleInfoAsync_FilterableIssueIdNull_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")); + Guid? issueId = null; + + await testSubject.GetRuleInfoAsync(_compositeRuleId, issueId); + + rulesServiceMock.Verify(x => x.GetEffectiveRuleDetailsAsync(It.Is(p => p.ruleKey == _compositeRuleId.ToString())), Times.Once); + issueServiceMock.Verify(x => x.GetEffectiveIssueDetailsAsync(It.IsAny()), Times.Never); + } + + [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()), Times.Never); + issueServiceMock.Verify(x => x.GetEffectiveIssueDetailsAsync(It.Is(p => p.issueId == issueId)), Times.Once); + } + + [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())) + .ThrowsAsync(new Exception("my message")); + + await testSubject.GetRuleInfoAsync(_compositeRuleId, issueId); + + rulesServiceMock.Verify(x => x.GetEffectiveRuleDetailsAsync(It.Is(p => p.ruleKey == _compositeRuleId.ToString())), Times.Once); + issueServiceMock.Verify(x => x.GetEffectiveIssueDetailsAsync(It.Is(p => p.issueId == issueId)), Times.Once); + } + + [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())) + .ThrowsAsync(new Exception("my message")); + rulesServiceMock + .Setup(x => x.GetEffectiveRuleDetailsAsync(It.IsAny())) + .ThrowsAsync(new Exception("my message")); + + var result = await testSubject.GetRuleInfoAsync(_compositeRuleId, issueId); + + result.Should().BeNull(); + rulesServiceMock.Verify(x => x.GetEffectiveRuleDetailsAsync(It.Is(p => p.ruleKey == _compositeRuleId.ToString())), Times.Once); + issueServiceMock.Verify(x => x.GetEffectiveIssueDetailsAsync(It.Is(p => p.issueId == issueId)), Times.Once); + } + private static void SetUpConfigScopeTracker( Mock configScopeTrackerMock, ConfigurationScope scope) => @@ -375,6 +703,15 @@ private static void SetupRulesService( p.ruleKey == rulekey && p.configurationScopeId == configScopeId))) .ReturnsAsync(new GetEffectiveRuleDetailsResponse(response)); + private static void SetupIssuesService( + Mock issuesServiceMock, + Guid id, + string configScopeId, + EffectiveIssueDetailsDto response) => + issuesServiceMock + .Setup(r => r.GetEffectiveIssueDetailsAsync(It.Is(p => p.configurationScopeId == configScopeId && p.issueId == id))) + .ReturnsAsync(new GetEffectiveIssueDetailsResponse(response)); + private static void SetUpServiceProvider( Mock serviceProviderMock, out Mock rulesServiceMock) @@ -384,6 +721,15 @@ private static void SetUpServiceProvider( serviceProviderMock.Setup(x => x.TryGetTransientService(out rulesService)).Returns(true); } + private static void SetUpIssueServiceProvider( + Mock serviceProviderMock, + out Mock rulesServiceMock) + { + rulesServiceMock = new Mock(); + var rulesService = rulesServiceMock.Object; + serviceProviderMock.Setup(x => x.TryGetTransientService(out rulesService)).Returns(true); + } + private static SLCoreRuleMetaDataProvider CreateTestSubject( out Mock serviceProviderMock, out Mock configScopeTrackerMock, @@ -394,4 +740,16 @@ private static SLCoreRuleMetaDataProvider CreateTestSubject( logger = new TestLogger(); return new SLCoreRuleMetaDataProvider(serviceProviderMock.Object, configScopeTrackerMock.Object, logger); } + + private static EffectiveIssueDetailsDto CreateEffectiveIssueDetailsDto(Either severityDetails, + Either description = default) => + new( + default, + default, + default, + default, + description, + default, + severityDetails, + default); } diff --git a/src/Education/Rule/IRuleMetaDataProvider.cs b/src/Education/Rule/IRuleMetaDataProvider.cs index 50f063940a..8974591ed0 100644 --- a/src/Education/Rule/IRuleMetaDataProvider.cs +++ b/src/Education/Rule/IRuleMetaDataProvider.cs @@ -18,9 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System.Threading; -using System.Threading.Tasks; using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.Suppressions; namespace SonarLint.VisualStudio.Education.Rule { @@ -31,5 +30,12 @@ public interface IRuleMetaDataProvider /// could not be found. /// Task GetRuleInfoAsync(SonarCompositeRuleId ruleId); + + /// + /// Returns rule information for the specified issue ID. + /// If is null, returns the rule information for the specified rule ID + /// If no rule information can be found, null is returned. + /// + Task GetRuleInfoAsync(SonarCompositeRuleId ruleId, Guid? issueId); } } diff --git a/src/Education/Rule/SLCoreRuleMetaDataProvider.cs b/src/Education/Rule/SLCoreRuleMetaDataProvider.cs index 323982b529..1c2337fd52 100644 --- a/src/Education/Rule/SLCoreRuleMetaDataProvider.cs +++ b/src/Education/Rule/SLCoreRuleMetaDataProvider.cs @@ -18,16 +18,14 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; using System.ComponentModel.Composition; -using System.Linq; -using System.Threading.Tasks; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Analysis; +using SonarLint.VisualStudio.Core.Suppressions; using SonarLint.VisualStudio.SLCore.Common.Helpers; using SonarLint.VisualStudio.SLCore.Common.Models; using SonarLint.VisualStudio.SLCore.Core; +using SonarLint.VisualStudio.SLCore.Service.Issue; using SonarLint.VisualStudio.SLCore.Service.Rules; using SonarLint.VisualStudio.SLCore.Service.Rules.Models; using SonarLint.VisualStudio.SLCore.State; @@ -74,7 +72,35 @@ public async Task GetRuleInfoAsync(SonarCompositeRuleId ruleId) return null; } - private static RuleInfo Convert(EffectiveRuleDetailsDto effectiveRuleDetailsAsync) => + /// + public async Task GetRuleInfoAsync(SonarCompositeRuleId ruleId, Guid? issueId) + { + var ruleInfoFromIssue = issueId != null ? await GetEffectiveIssueDetailsAsync(issueId.Value) : null; + + return ruleInfoFromIssue ?? await GetRuleInfoAsync(ruleId); + } + + internal async Task GetEffectiveIssueDetailsAsync(Guid issueId) + { + if (activeConfigScopeTracker.Current is { Id: var configurationScopeId } + && slCoreServiceProvider.TryGetTransientService(out IIssueSLCoreService rulesRpcService)) + { + try + { + var issueDetailsResponse = await rulesRpcService.GetEffectiveIssueDetailsAsync( + new GetEffectiveIssueDetailsParams(configurationScopeId, issueId)); + return Convert(issueDetailsResponse.details); + } + catch (Exception e) + { + logger.WriteLine(e.ToString()); + } + } + + return null; + } + + private static RuleInfo Convert(IRuleDetails effectiveRuleDetailsAsync) => new(effectiveRuleDetailsAsync.key, HtmlXmlCompatibilityHelper.EnsureHtmlIsXml(effectiveRuleDetailsAsync.description?.Left?.htmlContent), effectiveRuleDetailsAsync.name, From 0a113dc60d35e66e1a5aaac2afd7608f9a8b708d Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Wed, 13 Nov 2024 08:41:14 +0100 Subject: [PATCH 05/14] SLVS-1598 Extend Education to allow getting a rule help for an issue --- src/Core/IEducation.cs | 5 ++- src/Education.UnitTests/EducationTests.cs | 45 +++++++++++++------ .../SonarErrorListEventProcessorTests.cs | 2 +- .../Controls/RuleHelpUserControl.xaml.cs | 2 +- src/Education/Education.cs | 13 +++--- .../ErrorList/SonarErrorListEventProcessor.cs | 2 +- .../NavigateToRuleDescriptionCommandTests.cs | 2 +- .../NavigateToRuleDescriptionCommand.cs | 2 +- 8 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/Core/IEducation.cs b/src/Core/IEducation.cs index ac802a37da..ff7658d5e3 100644 --- a/src/Core/IEducation.cs +++ b/src/Core/IEducation.cs @@ -18,6 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using SonarLint.VisualStudio.Core.Suppressions; + namespace SonarLint.VisualStudio.Core { /// @@ -32,6 +34,7 @@ public interface IEducation /// be displayed in the IDE. Otherwise, the rule help will be displayed in the /// browser i.e. at rules.sonarsource.com /// Key for the How to fix it Context acquired from a specific issue. Can be null. - void ShowRuleHelp(SonarCompositeRuleId ruleId, string issueContext); + /// The SlCore issue ID for which the rule help should be shown.s + void ShowRuleHelp(SonarCompositeRuleId ruleId, string issueContext, Guid? issueId); } } diff --git a/src/Education.UnitTests/EducationTests.cs b/src/Education.UnitTests/EducationTests.cs index 8a0548916c..aebb70b01c 100644 --- a/src/Education.UnitTests/EducationTests.cs +++ b/src/Education.UnitTests/EducationTests.cs @@ -18,14 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Threading; using System.Windows.Documents; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Education.Commands; +using SonarLint.VisualStudio.Core.Suppressions; using SonarLint.VisualStudio.Education.Rule; using SonarLint.VisualStudio.Education.XamlGenerator; using SonarLint.VisualStudio.TestInfrastructure; @@ -53,7 +49,7 @@ public void ShowRuleHelp_KnownRule_DocumentIsDisplayedInToolWindow() var ruleId = new SonarCompositeRuleId("repoKey", "ruleKey"); var ruleInfo = Mock.Of(); - ruleMetaDataProvider.Setup(x => x.GetRuleInfoAsync(It.IsAny())).ReturnsAsync(ruleInfo); + ruleMetaDataProvider.Setup(x => x.GetRuleInfoAsync(It.IsAny(), It.IsAny())).ReturnsAsync(ruleInfo); var flowDocument = Mock.Of(); var ruleHelpXamlBuilder = new Mock(); @@ -74,9 +70,9 @@ public void ShowRuleHelp_KnownRule_DocumentIsDisplayedInToolWindow() toolWindowService.Invocations.Should().HaveCount(0); // Act - testSubject.ShowRuleHelp(ruleId, null); + testSubject.ShowRuleHelp(ruleId, null, null); - ruleMetaDataProvider.Verify(x => x.GetRuleInfoAsync(ruleId), Times.Once); + ruleMetaDataProvider.Verify(x => x.GetRuleInfoAsync(ruleId, It.IsAny()), Times.Once); ruleHelpXamlBuilder.Verify(x => x.Create(ruleInfo, /* todo */ null), Times.Once); ruleDescriptionToolWindow.Verify(x => x.UpdateContent(flowDocument), Times.Once); toolWindowService.Verify(x => x.Show(RuleHelpToolWindow.ToolWindowId), Times.Once); @@ -95,7 +91,7 @@ public void ShowRuleHelp_FailedToDisplayRule_RuleIsShownInBrowser() var ruleId = new SonarCompositeRuleId("repoKey", "ruleKey"); var ruleInfo = Mock.Of(); - ruleMetadataProvider.Setup(x => x.GetRuleInfoAsync(It.IsAny())).ReturnsAsync(ruleInfo); + ruleMetadataProvider.Setup(x => x.GetRuleInfoAsync(It.IsAny(), It.IsAny())).ReturnsAsync(ruleInfo); ruleHelpXamlBuilder.Setup(x => x.Create(ruleInfo, /* todo */ null)).Throws(new Exception("some layout error")); @@ -107,9 +103,9 @@ public void ShowRuleHelp_FailedToDisplayRule_RuleIsShownInBrowser() toolWindowService.Reset(); // Called in the constructor, so need to reset to clear the list of invocations - testSubject.ShowRuleHelp(ruleId, /* todo */ null); + testSubject.ShowRuleHelp(ruleId, /* todo */ null, null); - ruleMetadataProvider.Verify(x => x.GetRuleInfoAsync(ruleId), Times.Once); + ruleMetadataProvider.Verify(x => x.GetRuleInfoAsync(ruleId, It.IsAny()), Times.Once); showRuleInBrowser.Verify(x => x.ShowRuleDescription(ruleId), Times.Once); // should have attempted to build the rule, but failed @@ -126,7 +122,7 @@ public void ShowRuleHelp_UnknownRule_RuleIsShownInBrowser() var showRuleInBrowser = new Mock(); var unknownRule = new SonarCompositeRuleId("known", "xxx"); - ruleMetadataProvider.Setup(x => x.GetRuleInfoAsync(unknownRule)).ReturnsAsync((IRuleInfo)null); + ruleMetadataProvider.Setup(x => x.GetRuleInfoAsync(unknownRule, It.IsAny())).ReturnsAsync((IRuleInfo)null); var testSubject = CreateEducation( toolWindowService.Object, @@ -136,9 +132,9 @@ public void ShowRuleHelp_UnknownRule_RuleIsShownInBrowser() toolWindowService.Reset(); // Called in the constructor, so need to reset to clear the list of invocations - testSubject.ShowRuleHelp(unknownRule, /* todo */ null); + testSubject.ShowRuleHelp(unknownRule, /* todo */ null, null); - ruleMetadataProvider.Verify(x => x.GetRuleInfoAsync(unknownRule), Times.Once); + ruleMetadataProvider.Verify(x => x.GetRuleInfoAsync(unknownRule, It.IsAny()), Times.Once); showRuleInBrowser.Verify(x => x.ShowRuleDescription(unknownRule), Times.Once); // Should not have attempted to build the rule @@ -146,6 +142,27 @@ public void ShowRuleHelp_UnknownRule_RuleIsShownInBrowser() toolWindowService.Invocations.Should().HaveCount(0); } + [TestMethod] + public void ShowRuleHelp_FilterableIssueProvided_CallsGetRuleInfoForIssue() + { + var toolWindowService = new Mock(); + var ruleMetadataProvider = new Mock(); + var ruleHelpXamlBuilder = new Mock(); + var showRuleInBrowser = new Mock(); + 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,null, issueId); + + ruleMetadataProvider.Verify(x => x.GetRuleInfoAsync(ruleId, issueId), Times.Once); + } + private Education CreateEducation(IToolWindowService toolWindowService = null, IRuleMetaDataProvider ruleMetadataProvider = null, IShowRuleInBrowser showRuleInBrowser = null, diff --git a/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs b/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs index f81eeeb0c5..ee4a303983 100644 --- a/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs +++ b/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs @@ -68,7 +68,7 @@ public void PreprocessNavigateToHelp_IsASonarRule_EventIsHandledAndEducationServ errorListHelper.Verify(x => x.TryGetRuleId(handle, out ruleId)); education.Invocations.Should().HaveCount(1); - education.Verify(x => x.ShowRuleHelp(ruleId, /* todo */ null)); + education.Verify(x => x.ShowRuleHelp(ruleId, /* todo */ null, null)); eventArgs.Handled.Should().BeTrue(); } diff --git a/src/Education/Controls/RuleHelpUserControl.xaml.cs b/src/Education/Controls/RuleHelpUserControl.xaml.cs index 1eab623ec3..1566f7b904 100644 --- a/src/Education/Controls/RuleHelpUserControl.xaml.cs +++ b/src/Education/Controls/RuleHelpUserControl.xaml.cs @@ -57,7 +57,7 @@ public void HandleRequestNavigate(object sender, RequestNavigateEventArgs e) // in which case it needs to be handed over to the education service. if (SonarRuleIdUriEncoderDecoder.TryDecodeToCompositeRuleId(e.Uri, out SonarCompositeRuleId compositeRuleId)) { - education.ShowRuleHelp(compositeRuleId, null); + education.ShowRuleHelp(compositeRuleId, null, null); return; } diff --git a/src/Education/Education.cs b/src/Education/Education.cs index f1902994e7..418e0043af 100644 --- a/src/Education/Education.cs +++ b/src/Education/Education.cs @@ -18,13 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; using System.ComponentModel.Composition; -using System.Threading; -using System.Threading.Tasks; using Microsoft.VisualStudio.Threading; using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Education.Commands; +using SonarLint.VisualStudio.Core.Suppressions; using SonarLint.VisualStudio.Education.Rule; using SonarLint.VisualStudio.Education.XamlGenerator; using SonarLint.VisualStudio.Infrastructure.VS; @@ -68,16 +65,16 @@ public Education(IToolWindowService toolWindowService, IRuleMetaDataProvider rul this.threadHandling = threadHandling; } - public void ShowRuleHelp(SonarCompositeRuleId ruleId, string issueContext) + public void ShowRuleHelp(SonarCompositeRuleId ruleId, string issueContext, Guid? issueId) { - ShowRuleHelpAsync(ruleId, issueContext).Forget(); + ShowRuleHelpAsync(ruleId, issueContext, issueId).Forget(); } - private async Task ShowRuleHelpAsync(SonarCompositeRuleId ruleId, string issueContext) + private async Task ShowRuleHelpAsync(SonarCompositeRuleId ruleId, string issueContext, Guid? issueId) { await threadHandling.SwitchToBackgroundThread(); - var ruleInfo = await ruleMetadataProvider.GetRuleInfoAsync(ruleId); + var ruleInfo = await ruleMetadataProvider.GetRuleInfoAsync(ruleId, issueId); await threadHandling.RunOnUIThreadAsync(() => { diff --git a/src/Education/ErrorList/SonarErrorListEventProcessor.cs b/src/Education/ErrorList/SonarErrorListEventProcessor.cs index 539a2eb7f7..0f78b75c93 100644 --- a/src/Education/ErrorList/SonarErrorListEventProcessor.cs +++ b/src/Education/ErrorList/SonarErrorListEventProcessor.cs @@ -58,7 +58,7 @@ public override void PreprocessNavigateToHelp( { logger.LogVerbose(Resources.ErrorList_Processor_SonarRuleDetected, ruleId); - educationService.ShowRuleHelp(ruleId, /* todo */ null); + educationService.ShowRuleHelp(ruleId, /* todo */ null, null); // TODO // Mark the event as handled to stop the normal VS "show help in browser" behaviour handled = true; diff --git a/src/IssueViz.UnitTests/IssueVisualizationControl/ViewModelCommands/NavigateToRuleDescriptionCommandTests.cs b/src/IssueViz.UnitTests/IssueVisualizationControl/ViewModelCommands/NavigateToRuleDescriptionCommandTests.cs index e3baeedbc0..a604bd9b38 100644 --- a/src/IssueViz.UnitTests/IssueVisualizationControl/ViewModelCommands/NavigateToRuleDescriptionCommandTests.cs +++ b/src/IssueViz.UnitTests/IssueVisualizationControl/ViewModelCommands/NavigateToRuleDescriptionCommandTests.cs @@ -82,7 +82,7 @@ public void Execute_RuleDocumentationShown(string fullRuleKey) testSubject.Execute(executeParam); - educationService.Verify(x => x.ShowRuleHelp(It.IsAny(), /* todo */ null), Times.Once); + educationService.Verify(x => x.ShowRuleHelp(It.IsAny(), /* todo */ null, null), Times.Once); educationService.VerifyNoOtherCalls(); var actualRuleId = (SonarCompositeRuleId)educationService.Invocations[0].Arguments[0]; diff --git a/src/IssueViz/IssueVisualizationControl/ViewModels/Commands/NavigateToRuleDescriptionCommand.cs b/src/IssueViz/IssueVisualizationControl/ViewModels/Commands/NavigateToRuleDescriptionCommand.cs index f9de58f3ca..03b56bcd33 100644 --- a/src/IssueViz/IssueVisualizationControl/ViewModels/Commands/NavigateToRuleDescriptionCommand.cs +++ b/src/IssueViz/IssueVisualizationControl/ViewModels/Commands/NavigateToRuleDescriptionCommand.cs @@ -44,7 +44,7 @@ public NavigateToRuleDescriptionCommand(IEducation educationService) var paramObject = parameter as NavigateToRuleDescriptionCommandParam; if (SonarCompositeRuleId.TryParse(paramObject?.FullRuleKey, out var ruleId)) { - educationService.ShowRuleHelp(ruleId, paramObject?.Context); + educationService.ShowRuleHelp(ruleId, paramObject?.Context, null); // TODO check if we can provide the ID here } }, parameter => parameter is NavigateToRuleDescriptionCommandParam s && From 896307789ca407aafb197d2fce67601af168a696 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Wed, 13 Nov 2024 08:52:29 +0100 Subject: [PATCH 06/14] SLVS-1598 Show rule help for issue, when user clicks on rule key of an issue from VS Error list. --- .../SonarErrorListEventProcessorTests.cs | 23 +++++++++++++++++-- .../ErrorList/SonarErrorListEventProcessor.cs | 3 ++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs b/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs index ee4a303983..20db49d746 100644 --- a/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs +++ b/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs @@ -18,11 +18,10 @@ * 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.Core.Suppressions; using SonarLint.VisualStudio.Education.SonarLint.VisualStudio.Education.ErrorList; using SonarLint.VisualStudio.Infrastructure.VS; using SonarLint.VisualStudio.TestInfrastructure; @@ -72,6 +71,26 @@ public void PreprocessNavigateToHelp_IsASonarRule_EventIsHandledAndEducationServ eventArgs.Handled.Should().BeTrue(); } + [TestMethod] + [DataRow(true)] + [DataRow(false)] + public void PreprocessNavigateToHelp_IsASonarRule_EducationServiceIsCalledWithIssueId(bool getFilterableIssueResult) + { + SonarCompositeRuleId ruleId; + IFilterableIssue filterableIssue = new Mock().Object; + SonarCompositeRuleId.TryParse("cpp:S123", out ruleId); + var handle = Mock.Of(); + var errorListHelper = CreateErrorListHelper(isSonarRule: true, ruleId); + errorListHelper.Setup(x => x.TryGetFilterableIssue(It.IsAny(), out filterableIssue)).Returns(getFilterableIssueResult); + var education = new Mock(); + var testSubject = CreateTestSubject(education.Object, errorListHelper.Object); + + testSubject.PreprocessNavigateToHelp(handle, new TableEntryEventArgs()); + + education.Invocations.Should().HaveCount(1); + education.Verify(x => x.ShowRuleHelp(ruleId, null, filterableIssue.IssueId)); + } + private static Mock CreateErrorListHelper(bool isSonarRule, SonarCompositeRuleId ruleId) { var mock = new Mock(); diff --git a/src/Education/ErrorList/SonarErrorListEventProcessor.cs b/src/Education/ErrorList/SonarErrorListEventProcessor.cs index 0f78b75c93..86f23315b5 100644 --- a/src/Education/ErrorList/SonarErrorListEventProcessor.cs +++ b/src/Education/ErrorList/SonarErrorListEventProcessor.cs @@ -56,9 +56,10 @@ public override void PreprocessNavigateToHelp( if (errorListHelper.TryGetRuleId(entry, out var ruleId)) { + errorListHelper.TryGetFilterableIssue(entry, out var filterableIssue); logger.LogVerbose(Resources.ErrorList_Processor_SonarRuleDetected, ruleId); - educationService.ShowRuleHelp(ruleId, /* todo */ null, null); // TODO + educationService.ShowRuleHelp(ruleId, null, filterableIssue?.IssueId); // Mark the event as handled to stop the normal VS "show help in browser" behaviour handled = true; From 4bc2fcbe4e170b5f56bc7d290ad13412f09c178e Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Wed, 13 Nov 2024 13:08:58 +0100 Subject: [PATCH 07/14] SLVS-1598 Provide issue id to the NavigateToRuleDescriptionCommand --- .../HotspotsList/HotspotsControl.xaml | 1 + .../OpenInIDEHotspotsControl.xaml | 1 + .../Taint/TaintList/TaintIssuesControl.xaml | 1 + ...eToRuleDescriptionCommandConverterTests.cs | 39 ++++++++++++++++--- .../NavigateToRuleDescriptionCommandTests.cs | 16 +++++++- .../IssueVisualizationControl.xaml | 1 + .../NavigateToRuleDescriptionCommand.cs | 15 +++++-- 7 files changed, 64 insertions(+), 10 deletions(-) diff --git a/src/IssueViz.Security/Hotspots/HotspotsList/HotspotsControl.xaml b/src/IssueViz.Security/Hotspots/HotspotsList/HotspotsControl.xaml index 66f02160c2..fe0e54411a 100644 --- a/src/IssueViz.Security/Hotspots/HotspotsList/HotspotsControl.xaml +++ b/src/IssueViz.Security/Hotspots/HotspotsList/HotspotsControl.xaml @@ -99,6 +99,7 @@ + diff --git a/src/IssueViz.Security/OpenInIdeHotspots_List/HotspotsList/OpenInIDEHotspotsControl.xaml b/src/IssueViz.Security/OpenInIdeHotspots_List/HotspotsList/OpenInIDEHotspotsControl.xaml index ff53980ea3..c06d875ca5 100644 --- a/src/IssueViz.Security/OpenInIdeHotspots_List/HotspotsList/OpenInIDEHotspotsControl.xaml +++ b/src/IssueViz.Security/OpenInIdeHotspots_List/HotspotsList/OpenInIDEHotspotsControl.xaml @@ -116,6 +116,7 @@ + diff --git a/src/IssueViz.Security/Taint/TaintList/TaintIssuesControl.xaml b/src/IssueViz.Security/Taint/TaintList/TaintIssuesControl.xaml index 1b11b1a9e9..6b6a30f6c5 100644 --- a/src/IssueViz.Security/Taint/TaintList/TaintIssuesControl.xaml +++ b/src/IssueViz.Security/Taint/TaintList/TaintIssuesControl.xaml @@ -174,6 +174,7 @@ + diff --git a/src/IssueViz.UnitTests/IssueVisualizationControl/NavigateToRuleDescriptionCommandConverterTests.cs b/src/IssueViz.UnitTests/IssueVisualizationControl/NavigateToRuleDescriptionCommandConverterTests.cs index b5637c1565..2c2d648c18 100644 --- a/src/IssueViz.UnitTests/IssueVisualizationControl/NavigateToRuleDescriptionCommandConverterTests.cs +++ b/src/IssueViz.UnitTests/IssueVisualizationControl/NavigateToRuleDescriptionCommandConverterTests.cs @@ -18,8 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; using SonarLint.VisualStudio.IssueVisualization.IssueVisualizationControl.ViewModels.Commands; using SonarLint.VisualStudio.TestInfrastructure; @@ -43,13 +41,15 @@ public void Convert_CorrectFormat_Converts() } [TestMethod] - public void Convert_WrongNumberOfParams_ReturnsNull() + public void Convert_WrongNumberOfParams_IgnoresAdditionalParameter() { - var values = new object[] { "RuleKey", "context", "third param" }; + var values = new object[] { "RuleKey", "context", "third param", "fourth param" }; var command = testSubject.Convert(values, default, default, default); - command.Should().BeNull(); + (command is NavigateToRuleDescriptionCommandParam).Should().BeTrue(); + ((NavigateToRuleDescriptionCommandParam)command).FullRuleKey.Should().Be("RuleKey"); + ((NavigateToRuleDescriptionCommandParam)command).Context.Should().Be("context"); } [DataRow("str", 3)] @@ -90,5 +90,34 @@ public void ConvertBack_WrongType_ReturnsValues() values.Should().BeNull(); } } + + [TestMethod] + public void Convert_ThirdOptionalValueProvided_SetsIssueId() + { + var issuedId = Guid.NewGuid(); + var values = new object[] { "RuleKey", "context", issuedId }; + + var command = testSubject.Convert(values, default, default, default); + + var navigateToRuleDescriptionParam = command as NavigateToRuleDescriptionCommandParam; + navigateToRuleDescriptionParam.Should().NotBeNull(); + navigateToRuleDescriptionParam.FullRuleKey.Should().Be("RuleKey"); + navigateToRuleDescriptionParam.Context.Should().Be("context"); + navigateToRuleDescriptionParam.IssueId.Should().Be(issuedId); + } + + [TestMethod] + public void Convert_ThirdOptionalValueIsNull_SetsIssueIdToNull() + { + var values = new object[] { "RuleKey", "context", null }; + + var command = testSubject.Convert(values, default, default, default); + + var navigateToRuleDescriptionParam = command as NavigateToRuleDescriptionCommandParam; + navigateToRuleDescriptionParam.Should().NotBeNull(); + navigateToRuleDescriptionParam.FullRuleKey.Should().Be("RuleKey"); + navigateToRuleDescriptionParam.Context.Should().Be("context"); + navigateToRuleDescriptionParam.IssueId.Should().BeNull(); + } } } diff --git a/src/IssueViz.UnitTests/IssueVisualizationControl/ViewModelCommands/NavigateToRuleDescriptionCommandTests.cs b/src/IssueViz.UnitTests/IssueVisualizationControl/ViewModelCommands/NavigateToRuleDescriptionCommandTests.cs index a604bd9b38..21f28776c3 100644 --- a/src/IssueViz.UnitTests/IssueVisualizationControl/ViewModelCommands/NavigateToRuleDescriptionCommandTests.cs +++ b/src/IssueViz.UnitTests/IssueVisualizationControl/ViewModelCommands/NavigateToRuleDescriptionCommandTests.cs @@ -18,8 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.IssueVisualization.IssueVisualizationControl.ViewModels.Commands; @@ -103,6 +101,20 @@ public void Execute_WrongTypeParameter_DoesNotCrash() educationService.VerifyNoOtherCalls(); } + [TestMethod] + public void Execute_IssueIdProvided_RuleDocumentationShownForIssue() + { + var issueId = Guid.NewGuid(); + var educationService = new Mock(); + var testSubject = CreateTestSubject(educationService.Object); + var executeParam = new NavigateToRuleDescriptionCommandParam { FullRuleKey = "csharp:S100", IssueId = issueId}; + + testSubject.Execute(executeParam); + + educationService.Verify(x => x.ShowRuleHelp(It.IsAny(), null, issueId), Times.Once); + educationService.VerifyNoOtherCalls(); + } + private NavigateToRuleDescriptionCommand CreateTestSubject(IEducation educationService = null) { educationService ??= Mock.Of(); diff --git a/src/IssueViz/IssueVisualizationControl/IssueVisualizationControl.xaml b/src/IssueViz/IssueVisualizationControl/IssueVisualizationControl.xaml index f250640fd0..84a7df41b3 100644 --- a/src/IssueViz/IssueVisualizationControl/IssueVisualizationControl.xaml +++ b/src/IssueViz/IssueVisualizationControl/IssueVisualizationControl.xaml @@ -322,6 +322,7 @@ + diff --git a/src/IssueViz/IssueVisualizationControl/ViewModels/Commands/NavigateToRuleDescriptionCommand.cs b/src/IssueViz/IssueVisualizationControl/ViewModels/Commands/NavigateToRuleDescriptionCommand.cs index 03b56bcd33..73d74d9c29 100644 --- a/src/IssueViz/IssueVisualizationControl/ViewModels/Commands/NavigateToRuleDescriptionCommand.cs +++ b/src/IssueViz/IssueVisualizationControl/ViewModels/Commands/NavigateToRuleDescriptionCommand.cs @@ -44,7 +44,7 @@ public NavigateToRuleDescriptionCommand(IEducation educationService) var paramObject = parameter as NavigateToRuleDescriptionCommandParam; if (SonarCompositeRuleId.TryParse(paramObject?.FullRuleKey, out var ruleId)) { - educationService.ShowRuleHelp(ruleId, paramObject?.Context, null); // TODO check if we can provide the ID here + educationService.ShowRuleHelp(ruleId, paramObject?.Context, paramObject?.IssueId); } }, parameter => parameter is NavigateToRuleDescriptionCommandParam s && @@ -56,6 +56,10 @@ public NavigateToRuleDescriptionCommand(IEducation educationService) internal class NavigateToRuleDescriptionCommandParam { + /// + /// The id of the issue that comes from SlCore + /// + public Guid? IssueId { get; set; } public string FullRuleKey { get; set; } public string Context { get; set; } } @@ -64,9 +68,14 @@ public class NavigateToRuleDescriptionCommandConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { - if (values.Length == 2 && values[0] is string && (values[1] is string || values[1] == null)) + if (values.Length >= 2 && values[0] is string && (values[1] is string || values[1] == null)) { - return new NavigateToRuleDescriptionCommandParam { FullRuleKey = (string)values[0], Context = (string)values[1] }; + var parameters = new NavigateToRuleDescriptionCommandParam { FullRuleKey = (string)values[0], Context = (string)values[1] }; + if (values.Length == 3 && values[2] is Guid) + { + parameters.IssueId = (Guid)values[2]; + } + return parameters; } return null; } From 8cf9a9c7fb85775e3af1cd8fec37983f06f7d425 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Wed, 13 Nov 2024 17:11:19 +0100 Subject: [PATCH 08/14] SLVS-1598 Apply review feedback --- src/Core/Suppressions/FilterableRoslynIssue.cs | 2 +- src/Education/Rule/SLCoreRuleMetaDataProvider.cs | 1 - src/Infrastructure.VS/ErrorListHelper.cs | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Core/Suppressions/FilterableRoslynIssue.cs b/src/Core/Suppressions/FilterableRoslynIssue.cs index d6ab1f7a88..1bc9fcd74c 100644 --- a/src/Core/Suppressions/FilterableRoslynIssue.cs +++ b/src/Core/Suppressions/FilterableRoslynIssue.cs @@ -43,7 +43,7 @@ public FilterableRoslynIssue(string ruleId, string filePath, int startLine, int /// /// Always null, as this Id is specific to SlCore /// - public Guid? IssueId { get; } + public Guid? IssueId => null; public string RuleId { get; } public string FilePath { get; } public int? StartLine => RoslynStartLine; diff --git a/src/Education/Rule/SLCoreRuleMetaDataProvider.cs b/src/Education/Rule/SLCoreRuleMetaDataProvider.cs index 1c2337fd52..5740741f87 100644 --- a/src/Education/Rule/SLCoreRuleMetaDataProvider.cs +++ b/src/Education/Rule/SLCoreRuleMetaDataProvider.cs @@ -21,7 +21,6 @@ using System.ComponentModel.Composition; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Analysis; -using SonarLint.VisualStudio.Core.Suppressions; using SonarLint.VisualStudio.SLCore.Common.Helpers; using SonarLint.VisualStudio.SLCore.Common.Models; using SonarLint.VisualStudio.SLCore.Core; diff --git a/src/Infrastructure.VS/ErrorListHelper.cs b/src/Infrastructure.VS/ErrorListHelper.cs index 78554a53fe..4beb5f0b80 100644 --- a/src/Infrastructure.VS/ErrorListHelper.cs +++ b/src/Infrastructure.VS/ErrorListHelper.cs @@ -88,8 +88,7 @@ public bool TryGetIssueFromSelectedRow(out IFilterableIssue issue) { IFilterableIssue issueOut = null; var result = vSServiceOperation.Execute( - errorList => TryGetSelectedSnapshotAndIndex(errorList, out var snapshot, out var index) - && TryGetValue(snapshot, index, SonarLintTableControlConstants.IssueVizColumnName, out issueOut)); + errorList => TryGetSelectedTableEntry(errorList, out var handle) && TryGetFilterableIssue(handle, out issueOut)); issue = issueOut; From 32d24b602793b99d93f1299ff03847f85acb1656 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Wed, 13 Nov 2024 17:12:10 +0100 Subject: [PATCH 09/14] SLVS-1598 Move order of parameters --- src/Core/IEducation.cs | 2 +- src/Education.UnitTests/EducationTests.cs | 2 +- .../ErrorList/SonarErrorListEventProcessorTests.cs | 2 +- src/Education/Education.cs | 6 +++--- src/Education/ErrorList/SonarErrorListEventProcessor.cs | 2 +- .../NavigateToRuleDescriptionCommandTests.cs | 2 +- .../ViewModels/Commands/NavigateToRuleDescriptionCommand.cs | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Core/IEducation.cs b/src/Core/IEducation.cs index ff7658d5e3..2b848bd09f 100644 --- a/src/Core/IEducation.cs +++ b/src/Core/IEducation.cs @@ -35,6 +35,6 @@ public interface IEducation /// browser i.e. at rules.sonarsource.com /// Key for the How to fix it Context acquired from a specific issue. Can be null. /// The SlCore issue ID for which the rule help should be shown.s - void ShowRuleHelp(SonarCompositeRuleId ruleId, string issueContext, Guid? issueId); + void ShowRuleHelp(SonarCompositeRuleId ruleId, Guid? issueId, string issueContext); } } diff --git a/src/Education.UnitTests/EducationTests.cs b/src/Education.UnitTests/EducationTests.cs index aebb70b01c..14654e33bf 100644 --- a/src/Education.UnitTests/EducationTests.cs +++ b/src/Education.UnitTests/EducationTests.cs @@ -158,7 +158,7 @@ public void ShowRuleHelp_FilterableIssueProvided_CallsGetRuleInfoForIssue() showRuleInBrowser.Object, ruleHelpXamlBuilder.Object); - testSubject.ShowRuleHelp(ruleId,null, issueId); + testSubject.ShowRuleHelp(ruleId,issueId, null); ruleMetadataProvider.Verify(x => x.GetRuleInfoAsync(ruleId, issueId), Times.Once); } diff --git a/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs b/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs index 20db49d746..c9fc9c141c 100644 --- a/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs +++ b/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs @@ -88,7 +88,7 @@ public void PreprocessNavigateToHelp_IsASonarRule_EducationServiceIsCalledWithIs testSubject.PreprocessNavigateToHelp(handle, new TableEntryEventArgs()); education.Invocations.Should().HaveCount(1); - education.Verify(x => x.ShowRuleHelp(ruleId, null, filterableIssue.IssueId)); + education.Verify(x => x.ShowRuleHelp(ruleId, filterableIssue.IssueId, null)); } private static Mock CreateErrorListHelper(bool isSonarRule, SonarCompositeRuleId ruleId) diff --git a/src/Education/Education.cs b/src/Education/Education.cs index 418e0043af..c2bb19d406 100644 --- a/src/Education/Education.cs +++ b/src/Education/Education.cs @@ -65,12 +65,12 @@ public Education(IToolWindowService toolWindowService, IRuleMetaDataProvider rul this.threadHandling = threadHandling; } - public void ShowRuleHelp(SonarCompositeRuleId ruleId, string issueContext, Guid? issueId) + public void ShowRuleHelp(SonarCompositeRuleId ruleId, Guid? issueId, string issueContext) { - ShowRuleHelpAsync(ruleId, issueContext, issueId).Forget(); + ShowRuleHelpAsync(ruleId, issueId, issueContext).Forget(); } - private async Task ShowRuleHelpAsync(SonarCompositeRuleId ruleId, string issueContext, Guid? issueId) + private async Task ShowRuleHelpAsync(SonarCompositeRuleId ruleId, Guid? issueId, string issueContext) { await threadHandling.SwitchToBackgroundThread(); diff --git a/src/Education/ErrorList/SonarErrorListEventProcessor.cs b/src/Education/ErrorList/SonarErrorListEventProcessor.cs index 86f23315b5..7817bbb064 100644 --- a/src/Education/ErrorList/SonarErrorListEventProcessor.cs +++ b/src/Education/ErrorList/SonarErrorListEventProcessor.cs @@ -59,7 +59,7 @@ public override void PreprocessNavigateToHelp( errorListHelper.TryGetFilterableIssue(entry, out var filterableIssue); logger.LogVerbose(Resources.ErrorList_Processor_SonarRuleDetected, ruleId); - educationService.ShowRuleHelp(ruleId, null, filterableIssue?.IssueId); + educationService.ShowRuleHelp(ruleId, filterableIssue?.IssueId, null); // Mark the event as handled to stop the normal VS "show help in browser" behaviour handled = true; diff --git a/src/IssueViz.UnitTests/IssueVisualizationControl/ViewModelCommands/NavigateToRuleDescriptionCommandTests.cs b/src/IssueViz.UnitTests/IssueVisualizationControl/ViewModelCommands/NavigateToRuleDescriptionCommandTests.cs index 21f28776c3..9005d0464d 100644 --- a/src/IssueViz.UnitTests/IssueVisualizationControl/ViewModelCommands/NavigateToRuleDescriptionCommandTests.cs +++ b/src/IssueViz.UnitTests/IssueVisualizationControl/ViewModelCommands/NavigateToRuleDescriptionCommandTests.cs @@ -111,7 +111,7 @@ public void Execute_IssueIdProvided_RuleDocumentationShownForIssue() testSubject.Execute(executeParam); - educationService.Verify(x => x.ShowRuleHelp(It.IsAny(), null, issueId), Times.Once); + educationService.Verify(x => x.ShowRuleHelp(It.IsAny(), issueId, null), Times.Once); educationService.VerifyNoOtherCalls(); } diff --git a/src/IssueViz/IssueVisualizationControl/ViewModels/Commands/NavigateToRuleDescriptionCommand.cs b/src/IssueViz/IssueVisualizationControl/ViewModels/Commands/NavigateToRuleDescriptionCommand.cs index 73d74d9c29..a094284bd3 100644 --- a/src/IssueViz/IssueVisualizationControl/ViewModels/Commands/NavigateToRuleDescriptionCommand.cs +++ b/src/IssueViz/IssueVisualizationControl/ViewModels/Commands/NavigateToRuleDescriptionCommand.cs @@ -44,7 +44,7 @@ public NavigateToRuleDescriptionCommand(IEducation educationService) var paramObject = parameter as NavigateToRuleDescriptionCommandParam; if (SonarCompositeRuleId.TryParse(paramObject?.FullRuleKey, out var ruleId)) { - educationService.ShowRuleHelp(ruleId, paramObject?.Context, paramObject?.IssueId); + educationService.ShowRuleHelp(ruleId, paramObject?.IssueId, paramObject?.Context); } }, parameter => parameter is NavigateToRuleDescriptionCommandParam s && From a9405a7463fa456c60b53ffd71c7a210bc583685 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Thu, 14 Nov 2024 10:34:55 +0100 Subject: [PATCH 10/14] SLVS-1598 Expose just one method in the IRuleMetaDataProvider --- .../Rule/SLCoreRuleMetaDataProviderTests.cs | 87 +++++++++---------- src/Education/Rule/IRuleMetaDataProvider.cs | 11 +-- .../Rule/SLCoreRuleMetaDataProvider.cs | 36 ++++---- 3 files changed, 63 insertions(+), 71 deletions(-) diff --git a/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs b/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs index 6635249693..0372a27e16 100644 --- a/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs +++ b/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs @@ -20,7 +20,6 @@ using Moq; using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Suppressions; using SonarLint.VisualStudio.Education.Rule; using SonarLint.VisualStudio.SLCore.Common.Models; using SonarLint.VisualStudio.SLCore.Core; @@ -44,7 +43,7 @@ namespace SonarLint.VisualStudio.Education.UnitTests.Rule; [TestClass] public class SLCoreRuleMetaDataProviderTests { - private static readonly SonarCompositeRuleId _compositeRuleId = new("rule", "key1"); + private static readonly SonarCompositeRuleId CompositeRuleId = new("rule", "key1"); [TestMethod] public void MefCtor_CheckIsExported() => @@ -80,7 +79,7 @@ public async Task GetRuleInfoAsync_CorrectlyConvertsSeverity(IssueSeverity slCor default, default)); - var ruleInfo = await testSubject.GetRuleInfoAsync(new SonarCompositeRuleId("rule", "key1")); + var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId); ruleInfo.Severity.Should().Be(expected); } @@ -108,7 +107,7 @@ public async Task GetRuleInfoAsync_CorrectlyConvertsType(RuleType slCore, RuleIs default, default)); - var ruleInfo = await testSubject.GetRuleInfoAsync(new SonarCompositeRuleId("rule", "key1")); + var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId); ruleInfo.IssueType.Should().Be(expected); } @@ -146,7 +145,7 @@ public async Task GetRuleInfoAsync_CorrectlyConvertsCleanCodeAttribute(CleanCode default, default)); - var ruleInfo = await testSubject.GetRuleInfoAsync(new SonarCompositeRuleId("rule", "key1")); + var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId); ruleInfo.CleanCodeAttribute.Should().Be(expected); } @@ -174,7 +173,7 @@ public async Task GetRuleInfoAsync_CorrectlyConvertsImpacts() default, default)); - var ruleInfo = await testSubject.GetRuleInfoAsync(new SonarCompositeRuleId("rule", "key1")); + var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId); ruleInfo.DefaultImpacts.Should().BeEquivalentTo(new Dictionary { @@ -204,7 +203,7 @@ public async Task GetRuleInfoAsync_Standard_SimpleRuleDescription() new RuleMonolithicDescriptionDto("content")), new List())); - var ruleInfo = await testSubject.GetRuleInfoAsync(new SonarCompositeRuleId("rule", "key1")); + var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId); ruleInfo.Should().BeEquivalentTo(new RuleInfo(rulekey, "content", @@ -239,7 +238,7 @@ public async Task GetRuleInfoAsync_MQR_SimpleRuleDescription() new RuleMonolithicDescriptionDto("content")), new List())); - var ruleInfo = await testSubject.GetRuleInfoAsync(new SonarCompositeRuleId("rule", "key1")); + var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId); ruleInfo.Should().BeEquivalentTo(new RuleInfo(rulekey, "content", @@ -274,7 +273,7 @@ public async Task GetRuleInfoAsync_Standard_RichRuleDescription() Either.CreateRight(ruleSplitDescriptionDto), new List { new("ignored", default, default, default) })); - var ruleInfo = await testSubject.GetRuleInfoAsync(new SonarCompositeRuleId("rule", "key1")); + var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId); ruleInfo.Should().BeEquivalentTo(new RuleInfo(rulekey, null, @@ -309,7 +308,7 @@ public async Task GetRuleInfoAsync_MQR_RichRuleDescription() Either.CreateRight(ruleSplitDescriptionDto), new List { new("ignored", default, default, default) })); - var ruleInfo = await testSubject.GetRuleInfoAsync(new SonarCompositeRuleId("rule", "key1")); + var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId); ruleInfo.Should().BeEquivalentTo(new RuleInfo(rulekey, null, @@ -330,7 +329,7 @@ public async Task GetRuleInfoAsync_NoActiveScope_ReturnsNull() SetUpServiceProvider(serviceProviderMock, out _); SetUpConfigScopeTracker(configScopeTrackerMock, null); - var ruleInfo = await testSubject.GetRuleInfoAsync(new SonarCompositeRuleId("rule", "key1")); + var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId); ruleInfo.Should().BeNull(); logger.AssertNoOutputMessages(); @@ -342,7 +341,7 @@ public async Task GetRuleInfoAsync_ServiceUnavailable_ReturnsNull() var testSubject = CreateTestSubject(out _, out var configScopeTrackerMock, out var logger); SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("id")); - var ruleInfo = await testSubject.GetRuleInfoAsync(new SonarCompositeRuleId("rule", "key1")); + var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId); ruleInfo.Should().BeNull(); logger.AssertNoOutputMessages(); @@ -359,7 +358,7 @@ public void GetRuleInfoAsync_ServiceThrows_ReturnsNullAndLogs() .Setup(x => x.GetEffectiveRuleDetailsAsync(It.IsAny())) .ThrowsAsync(new Exception("my message")); - var act = () => testSubject.GetRuleInfoAsync(new SonarCompositeRuleId("rule", "key1")); + var act = () => testSubject.GetRuleInfoAsync(CompositeRuleId); act.Should().NotThrow(); logger.AssertPartialOutputStringExists("my message"); @@ -371,7 +370,7 @@ public void GetRuleInfoAsync_ServiceThrows_ReturnsNullAndLogs() [DataRow(IssueSeverity.BLOCKER, RuleIssueSeverity.Blocker)] [DataRow(IssueSeverity.CRITICAL, RuleIssueSeverity.Critical)] [DataRow(IssueSeverity.MINOR, RuleIssueSeverity.Minor)] - public async Task GetEffectiveIssueDetailsAsync_CorrectlyConvertsSeverity(IssueSeverity slCore, RuleIssueSeverity expected) + public async Task GetRuleInfoAsync_ForIssue_CorrectlyConvertsSeverity(IssueSeverity slCore, RuleIssueSeverity expected) { var issueId = Guid.NewGuid(); var testSubject = @@ -380,7 +379,7 @@ public async Task GetEffectiveIssueDetailsAsync_CorrectlyConvertsSeverity(IssueS SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); SetupIssuesService(issuesServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new StandardModeDetails(slCore, default))); - var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(issueId); + var ruleInfo = await testSubject.GetRuleInfoAsync(default, issueId); ruleInfo.Severity.Should().Be(expected); } @@ -390,7 +389,7 @@ public async Task GetEffectiveIssueDetailsAsync_CorrectlyConvertsSeverity(IssueS [DataRow(RuleType.VULNERABILITY, RuleIssueType.Vulnerability)] [DataRow(RuleType.BUG, RuleIssueType.Bug)] [DataRow(RuleType.SECURITY_HOTSPOT, RuleIssueType.Hotspot)] - public async Task GetEffectiveIssueDetailsAsync_CorrectlyConvertsType(RuleType slCore, RuleIssueType expected) + public async Task GetRuleInfoAsync_ForIssue_CorrectlyConvertsType(RuleType slCore, RuleIssueType expected) { var issueId = Guid.NewGuid(); var testSubject = @@ -399,7 +398,7 @@ public async Task GetEffectiveIssueDetailsAsync_CorrectlyConvertsType(RuleType s SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); SetupIssuesService(issueServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new StandardModeDetails(default, slCore))); - var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(issueId); + var ruleInfo = await testSubject.GetRuleInfoAsync(default,issueId); ruleInfo.IssueType.Should().Be(expected); } @@ -419,7 +418,7 @@ public async Task GetEffectiveIssueDetailsAsync_CorrectlyConvertsType(RuleType s [DataRow(CleanCodeAttribute.LAWFUL, RuleCleanCodeAttribute.Lawful)] [DataRow(CleanCodeAttribute.RESPECTFUL, RuleCleanCodeAttribute.Respectful)] [DataRow(CleanCodeAttribute.TRUSTWORTHY, RuleCleanCodeAttribute.Trustworthy)] - public async Task GetEffectiveIssueDetailsAsync_CorrectlyConvertsCleanCodeAttribute(CleanCodeAttribute slCore, RuleCleanCodeAttribute expected) + public async Task GetRuleInfoAsync_ForIssue_CorrectlyConvertsCleanCodeAttribute(CleanCodeAttribute slCore, RuleCleanCodeAttribute expected) { var issueId = Guid.NewGuid(); var testSubject = @@ -428,13 +427,13 @@ public async Task GetEffectiveIssueDetailsAsync_CorrectlyConvertsCleanCodeAttrib SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); SetupIssuesService(issueServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new MQRModeDetails(slCore, default))); - var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(issueId); + var ruleInfo = await testSubject.GetRuleInfoAsync(default,issueId); ruleInfo.CleanCodeAttribute.Should().Be(expected); } [TestMethod] - public async Task GetEffectiveIssueDetailsAsync_CorrectlyConvertsImpacts() + public async Task GetRuleInfoAsync_ForIssue_CorrectlyConvertsImpacts() { var issueId = Guid.NewGuid(); var testSubject = @@ -447,7 +446,7 @@ public async Task GetEffectiveIssueDetailsAsync_CorrectlyConvertsImpacts() new ImpactDto(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.MEDIUM) ]))); - var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(issueId); + var ruleInfo = await testSubject.GetRuleInfoAsync(default,issueId); ruleInfo.DefaultImpacts.Should().BeEquivalentTo(new Dictionary { @@ -458,7 +457,7 @@ public async Task GetEffectiveIssueDetailsAsync_CorrectlyConvertsImpacts() } [TestMethod] - public async Task GetEffectiveIssueDetailsAsync_Standard_SimpleRuleDescription() + public async Task GetRuleInfoAsync_ForIssue_Standard_SimpleRuleDescription() { var issueId = Guid.NewGuid(); var testSubject = @@ -469,7 +468,7 @@ public async Task GetEffectiveIssueDetailsAsync_Standard_SimpleRuleDescription() Either.CreateLeft( new RuleMonolithicDescriptionDto("content")))); - var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(issueId); + var ruleInfo = await testSubject.GetRuleInfoAsync(default,issueId); ruleInfo.Should().BeEquivalentTo(new RuleInfo(null, "content", @@ -482,7 +481,7 @@ public async Task GetEffectiveIssueDetailsAsync_Standard_SimpleRuleDescription() } [TestMethod] - public async Task GetEffectiveIssueDetailsAsync_MQR_SimpleRuleDescription() + public async Task GetRuleInfoAsync_ForIssue_MQR_SimpleRuleDescription() { var issueId = Guid.NewGuid(); var testSubject = @@ -492,7 +491,7 @@ public async Task GetEffectiveIssueDetailsAsync_MQR_SimpleRuleDescription() SetupIssuesService(issueServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new MQRModeDetails(CleanCodeAttribute.MODULAR, default), Either.CreateLeft( new RuleMonolithicDescriptionDto("content")))); - var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(issueId); + var ruleInfo = await testSubject.GetRuleInfoAsync(default,issueId); ruleInfo.Should().BeEquivalentTo(new RuleInfo(null, "content", @@ -505,7 +504,7 @@ public async Task GetEffectiveIssueDetailsAsync_MQR_SimpleRuleDescription() } [TestMethod] - public async Task GetEffectiveIssueDetailsAsync_Standard_RichRuleDescription() + public async Task GetRuleInfoAsync_ForIssue_Standard_RichRuleDescription() { var issueId = Guid.NewGuid(); var testSubject = @@ -516,7 +515,7 @@ public async Task GetEffectiveIssueDetailsAsync_Standard_RichRuleDescription() SetupIssuesService(issueServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new StandardModeDetails(IssueSeverity.MINOR, RuleType.BUG), Either.CreateRight(ruleSplitDescriptionDto))); - var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(issueId); + var ruleInfo = await testSubject.GetRuleInfoAsync(default,issueId); ruleInfo.Should().BeEquivalentTo(new RuleInfo(null, null, @@ -530,7 +529,7 @@ public async Task GetEffectiveIssueDetailsAsync_Standard_RichRuleDescription() } [TestMethod] - public async Task GetEffectiveIssueDetailsAsync_MQR_RichRuleDescription() + public async Task GetRuleInfoAsync_ForIssue_MQR_RichRuleDescription() { var issueId = Guid.NewGuid(); var testSubject = @@ -541,7 +540,7 @@ public async Task GetEffectiveIssueDetailsAsync_MQR_RichRuleDescription() SetupIssuesService(issueServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new MQRModeDetails(CleanCodeAttribute.RESPECTFUL, default), Either.CreateRight(ruleSplitDescriptionDto))); - var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(issueId); + var ruleInfo = await testSubject.GetRuleInfoAsync(default,issueId); ruleInfo.Should().BeEquivalentTo(new RuleInfo(null, null, @@ -555,7 +554,7 @@ public async Task GetEffectiveIssueDetailsAsync_MQR_RichRuleDescription() } [TestMethod] - public async Task GetEffectiveIssueDetailsAsync_NoActiveScope_ReturnsNull() + public async Task GetRuleInfoAsync_ForIssue_NoActiveScope_ReturnsNull() { var testSubject = CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out var logger); @@ -563,26 +562,26 @@ public async Task GetEffectiveIssueDetailsAsync_NoActiveScope_ReturnsNull() SetUpConfigScopeTracker(configScopeTrackerMock, null); var issueId = Guid.NewGuid(); - var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(issueId); + var ruleInfo = await testSubject.GetRuleInfoAsync(default,issueId); ruleInfo.Should().BeNull(); logger.AssertNoOutputMessages(); } [TestMethod] - public async Task GetEffectiveIssueDetailsAsync_ServiceUnavailable_ReturnsNull() + public async Task GetRuleInfoAsync_ForIssue_ServiceUnavailable_ReturnsNull() { var testSubject = CreateTestSubject(out _, out var configScopeTrackerMock, out var logger); SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("id")); - var ruleInfo = await testSubject.GetEffectiveIssueDetailsAsync(Guid.NewGuid()); + var ruleInfo = await testSubject.GetRuleInfoAsync(default,Guid.NewGuid()); ruleInfo.Should().BeNull(); logger.AssertNoOutputMessages(); } [TestMethod] - public void GetEffectiveIssueDetailsAsync_ServiceThrows_ReturnsNullAndLogs() + public void GetRuleInfoAsync_ForIssue_ServiceThrows_ReturnsNullAndLogs() { var testSubject = CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out var logger); @@ -592,7 +591,7 @@ public void GetEffectiveIssueDetailsAsync_ServiceThrows_ReturnsNullAndLogs() .Setup(x => x.GetEffectiveIssueDetailsAsync(It.IsAny())) .ThrowsAsync(new Exception("my message")); - var act = () => testSubject.GetEffectiveIssueDetailsAsync(Guid.NewGuid()); + var act = () => testSubject.GetRuleInfoAsync(default,Guid.NewGuid()); act.Should().NotThrow(); logger.AssertPartialOutputStringExists("my message"); @@ -607,9 +606,9 @@ public async Task GetRuleInfoAsync_FilterableIssueNull_CallsGetEffectiveRuleDeta 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(p => p.ruleKey == _compositeRuleId.ToString())), Times.Once); + rulesServiceMock.Verify(x => x.GetEffectiveRuleDetailsAsync(It.Is(p => p.ruleKey == CompositeRuleId.ToString())), Times.Once); issueServiceMock.Verify(x => x.GetEffectiveIssueDetailsAsync(It.IsAny()), Times.Never); } @@ -623,9 +622,9 @@ public async Task GetRuleInfoAsync_FilterableIssueIdNull_CallsGetEffectiveRuleDe SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); Guid? issueId = null; - await testSubject.GetRuleInfoAsync(_compositeRuleId, issueId); + await testSubject.GetRuleInfoAsync(CompositeRuleId, issueId); - rulesServiceMock.Verify(x => x.GetEffectiveRuleDetailsAsync(It.Is(p => p.ruleKey == _compositeRuleId.ToString())), Times.Once); + rulesServiceMock.Verify(x => x.GetEffectiveRuleDetailsAsync(It.Is(p => p.ruleKey == CompositeRuleId.ToString())), Times.Once); issueServiceMock.Verify(x => x.GetEffectiveIssueDetailsAsync(It.IsAny()), Times.Never); } @@ -640,7 +639,7 @@ public async Task GetRuleInfoAsync_FilterableIssueIdNotNull_CallsGetEffectiveIss SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope(configScopeId)); SetupIssuesService(issueServiceMock, issueId, configScopeId, CreateEffectiveIssueDetailsDto(new MQRModeDetails(default, default))); - await testSubject.GetRuleInfoAsync(_compositeRuleId, issueId); + await testSubject.GetRuleInfoAsync(CompositeRuleId, issueId); rulesServiceMock.Verify(x => x.GetEffectiveRuleDetailsAsync(It.IsAny()), Times.Never); issueServiceMock.Verify(x => x.GetEffectiveIssueDetailsAsync(It.Is(p => p.issueId == issueId)), Times.Once); @@ -659,9 +658,9 @@ public async Task GetRuleInfoAsync_GetEffectiveIssueDetailsAsyncThrows_CallsGetE .Setup(x => x.GetEffectiveIssueDetailsAsync(It.IsAny())) .ThrowsAsync(new Exception("my message")); - await testSubject.GetRuleInfoAsync(_compositeRuleId, issueId); + await testSubject.GetRuleInfoAsync(CompositeRuleId, issueId); - rulesServiceMock.Verify(x => x.GetEffectiveRuleDetailsAsync(It.Is(p => p.ruleKey == _compositeRuleId.ToString())), Times.Once); + rulesServiceMock.Verify(x => x.GetEffectiveRuleDetailsAsync(It.Is(p => p.ruleKey == CompositeRuleId.ToString())), Times.Once); issueServiceMock.Verify(x => x.GetEffectiveIssueDetailsAsync(It.Is(p => p.issueId == issueId)), Times.Once); } @@ -681,10 +680,10 @@ public async Task GetRuleInfoAsync_BothServicesThrow_ReturnsNull() .Setup(x => x.GetEffectiveRuleDetailsAsync(It.IsAny())) .ThrowsAsync(new Exception("my message")); - var result = await testSubject.GetRuleInfoAsync(_compositeRuleId, issueId); + var result = await testSubject.GetRuleInfoAsync(CompositeRuleId, issueId); result.Should().BeNull(); - rulesServiceMock.Verify(x => x.GetEffectiveRuleDetailsAsync(It.Is(p => p.ruleKey == _compositeRuleId.ToString())), Times.Once); + rulesServiceMock.Verify(x => x.GetEffectiveRuleDetailsAsync(It.Is(p => p.ruleKey == CompositeRuleId.ToString())), Times.Once); issueServiceMock.Verify(x => x.GetEffectiveIssueDetailsAsync(It.Is(p => p.issueId == issueId)), Times.Once); } diff --git a/src/Education/Rule/IRuleMetaDataProvider.cs b/src/Education/Rule/IRuleMetaDataProvider.cs index 8974591ed0..bf1c9e056e 100644 --- a/src/Education/Rule/IRuleMetaDataProvider.cs +++ b/src/Education/Rule/IRuleMetaDataProvider.cs @@ -19,23 +19,16 @@ */ using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Suppressions; namespace SonarLint.VisualStudio.Education.Rule { public interface IRuleMetaDataProvider { /// - /// Returns rule information for the specified rule ID, or null if a rule description - /// could not be found. - /// - Task GetRuleInfoAsync(SonarCompositeRuleId ruleId); - - /// - /// Returns rule information for the specified issue ID. + /// If is NOT null, returns rule information for the specified issue ID /// If is null, returns the rule information for the specified rule ID /// If no rule information can be found, null is returned. /// - Task GetRuleInfoAsync(SonarCompositeRuleId ruleId, Guid? issueId); + Task GetRuleInfoAsync(SonarCompositeRuleId ruleId, Guid? issueId = null); } } diff --git a/src/Education/Rule/SLCoreRuleMetaDataProvider.cs b/src/Education/Rule/SLCoreRuleMetaDataProvider.cs index 5740741f87..511f26ec33 100644 --- a/src/Education/Rule/SLCoreRuleMetaDataProvider.cs +++ b/src/Education/Rule/SLCoreRuleMetaDataProvider.cs @@ -51,16 +51,24 @@ public SLCoreRuleMetaDataProvider(ISLCoreServiceProvider slCoreServiceProvider, this.logger = logger; } - public async Task GetRuleInfoAsync(SonarCompositeRuleId ruleId) + /// + public async Task GetRuleInfoAsync(SonarCompositeRuleId ruleId, Guid? issueId = null) + { + var ruleInfoFromIssue = issueId != null ? await GetEffectiveIssueDetailsAsync(issueId.Value) : null; + + return ruleInfoFromIssue ?? await GetEffectiveRuleDetailsAsync(ruleId); + } + + private async Task GetEffectiveIssueDetailsAsync(Guid issueId) { if (activeConfigScopeTracker.Current is { Id: var configurationScopeId } - && slCoreServiceProvider.TryGetTransientService(out IRulesSLCoreService rulesRpcService)) + && slCoreServiceProvider.TryGetTransientService(out IIssueSLCoreService rulesRpcService)) { try { - var ruleDetailsResponse = await rulesRpcService.GetEffectiveRuleDetailsAsync( - new GetEffectiveRuleDetailsParams(configurationScopeId, ruleId.ToString())); - return Convert(ruleDetailsResponse.details); + var issueDetailsResponse = await rulesRpcService.GetEffectiveIssueDetailsAsync( + new GetEffectiveIssueDetailsParams(configurationScopeId, issueId)); + return Convert(issueDetailsResponse.details); } catch (Exception e) { @@ -71,24 +79,16 @@ public async Task GetRuleInfoAsync(SonarCompositeRuleId ruleId) return null; } - /// - public async Task GetRuleInfoAsync(SonarCompositeRuleId ruleId, Guid? issueId) - { - var ruleInfoFromIssue = issueId != null ? await GetEffectiveIssueDetailsAsync(issueId.Value) : null; - - return ruleInfoFromIssue ?? await GetRuleInfoAsync(ruleId); - } - - internal async Task GetEffectiveIssueDetailsAsync(Guid issueId) + private async Task GetEffectiveRuleDetailsAsync(SonarCompositeRuleId ruleId) { if (activeConfigScopeTracker.Current is { Id: var configurationScopeId } - && slCoreServiceProvider.TryGetTransientService(out IIssueSLCoreService rulesRpcService)) + && slCoreServiceProvider.TryGetTransientService(out IRulesSLCoreService rulesRpcService)) { try { - var issueDetailsResponse = await rulesRpcService.GetEffectiveIssueDetailsAsync( - new GetEffectiveIssueDetailsParams(configurationScopeId, issueId)); - return Convert(issueDetailsResponse.details); + var ruleDetailsResponse = await rulesRpcService.GetEffectiveRuleDetailsAsync( + new GetEffectiveRuleDetailsParams(configurationScopeId, ruleId.ToString())); + return Convert(ruleDetailsResponse.details); } catch (Exception e) { From 3c78cffee6271497d9fa0e48e56c3b8b8b84aac8 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Thu, 14 Nov 2024 10:46:10 +0100 Subject: [PATCH 11/14] SLVS-1598 Link task to todos --- src/Education.UnitTests/EducationTests.cs | 10 +++++----- .../ErrorList/SonarErrorListEventProcessorTests.cs | 2 +- .../ErrorList/SonarErrorListEventProcessor.cs | 2 +- .../NavigateToRuleDescriptionCommandTests.cs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Education.UnitTests/EducationTests.cs b/src/Education.UnitTests/EducationTests.cs index 14654e33bf..c049485d9e 100644 --- a/src/Education.UnitTests/EducationTests.cs +++ b/src/Education.UnitTests/EducationTests.cs @@ -53,7 +53,7 @@ public void ShowRuleHelp_KnownRule_DocumentIsDisplayedInToolWindow() var flowDocument = Mock.Of(); var ruleHelpXamlBuilder = new Mock(); - ruleHelpXamlBuilder.Setup(x => x.Create(ruleInfo, /* todo */ null)).Returns(flowDocument); + ruleHelpXamlBuilder.Setup(x => x.Create(ruleInfo, /* todo by SLVS-1630 */ null)).Returns(flowDocument); var ruleDescriptionToolWindow = new Mock(); @@ -73,7 +73,7 @@ public void ShowRuleHelp_KnownRule_DocumentIsDisplayedInToolWindow() testSubject.ShowRuleHelp(ruleId, null, null); ruleMetaDataProvider.Verify(x => x.GetRuleInfoAsync(ruleId, It.IsAny()), Times.Once); - ruleHelpXamlBuilder.Verify(x => x.Create(ruleInfo, /* todo */ null), 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); @@ -93,7 +93,7 @@ public void ShowRuleHelp_FailedToDisplayRule_RuleIsShownInBrowser() var ruleInfo = Mock.Of(); ruleMetadataProvider.Setup(x => x.GetRuleInfoAsync(It.IsAny(), It.IsAny())).ReturnsAsync(ruleInfo); - ruleHelpXamlBuilder.Setup(x => x.Create(ruleInfo, /* todo */ null)).Throws(new Exception("some layout error")); + ruleHelpXamlBuilder.Setup(x => x.Create(ruleInfo, /* todo by SLVS-1630 */ null)).Throws(new Exception("some layout error")); var testSubject = CreateEducation( toolWindowService.Object, @@ -103,7 +103,7 @@ public void ShowRuleHelp_FailedToDisplayRule_RuleIsShownInBrowser() toolWindowService.Reset(); // Called in the constructor, so need to reset to clear the list of invocations - testSubject.ShowRuleHelp(ruleId, /* todo */ null, null); + testSubject.ShowRuleHelp(ruleId, null, /* todo by SLVS-1630 */null); ruleMetadataProvider.Verify(x => x.GetRuleInfoAsync(ruleId, It.IsAny()), Times.Once); showRuleInBrowser.Verify(x => x.ShowRuleDescription(ruleId), Times.Once); @@ -132,7 +132,7 @@ public void ShowRuleHelp_UnknownRule_RuleIsShownInBrowser() toolWindowService.Reset(); // Called in the constructor, so need to reset to clear the list of invocations - testSubject.ShowRuleHelp(unknownRule, /* todo */ null, null); + testSubject.ShowRuleHelp(unknownRule, null, /* todo by SLVS-1630 */ null); ruleMetadataProvider.Verify(x => x.GetRuleInfoAsync(unknownRule, It.IsAny()), Times.Once); showRuleInBrowser.Verify(x => x.ShowRuleDescription(unknownRule), Times.Once); diff --git a/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs b/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs index c9fc9c141c..8ce2c980d8 100644 --- a/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs +++ b/src/Education.UnitTests/ErrorList/SonarErrorListEventProcessorTests.cs @@ -67,7 +67,7 @@ public void PreprocessNavigateToHelp_IsASonarRule_EventIsHandledAndEducationServ errorListHelper.Verify(x => x.TryGetRuleId(handle, out ruleId)); education.Invocations.Should().HaveCount(1); - education.Verify(x => x.ShowRuleHelp(ruleId, /* todo */ null, null)); + education.Verify(x => x.ShowRuleHelp(ruleId, null, /* todo by SLVS-1630 */ null)); eventArgs.Handled.Should().BeTrue(); } diff --git a/src/Education/ErrorList/SonarErrorListEventProcessor.cs b/src/Education/ErrorList/SonarErrorListEventProcessor.cs index 7817bbb064..5c3828a5a2 100644 --- a/src/Education/ErrorList/SonarErrorListEventProcessor.cs +++ b/src/Education/ErrorList/SonarErrorListEventProcessor.cs @@ -59,7 +59,7 @@ public override void PreprocessNavigateToHelp( errorListHelper.TryGetFilterableIssue(entry, out var filterableIssue); logger.LogVerbose(Resources.ErrorList_Processor_SonarRuleDetected, ruleId); - educationService.ShowRuleHelp(ruleId, filterableIssue?.IssueId, null); + educationService.ShowRuleHelp(ruleId, filterableIssue?.IssueId, /* todo by SLVS-1630 */null); // Mark the event as handled to stop the normal VS "show help in browser" behaviour handled = true; diff --git a/src/IssueViz.UnitTests/IssueVisualizationControl/ViewModelCommands/NavigateToRuleDescriptionCommandTests.cs b/src/IssueViz.UnitTests/IssueVisualizationControl/ViewModelCommands/NavigateToRuleDescriptionCommandTests.cs index 9005d0464d..d732bbf95f 100644 --- a/src/IssueViz.UnitTests/IssueVisualizationControl/ViewModelCommands/NavigateToRuleDescriptionCommandTests.cs +++ b/src/IssueViz.UnitTests/IssueVisualizationControl/ViewModelCommands/NavigateToRuleDescriptionCommandTests.cs @@ -80,7 +80,7 @@ public void Execute_RuleDocumentationShown(string fullRuleKey) testSubject.Execute(executeParam); - educationService.Verify(x => x.ShowRuleHelp(It.IsAny(), /* todo */ null, null), Times.Once); + educationService.Verify(x => x.ShowRuleHelp(It.IsAny(),null, /* todo by SLVS-1630 */null), Times.Once); educationService.VerifyNoOtherCalls(); var actualRuleId = (SonarCompositeRuleId)educationService.Invocations[0].Arguments[0]; From 33298838ab0e41548aa69cdd9fdab78cd92461db Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Thu, 14 Nov 2024 11:21:25 +0100 Subject: [PATCH 12/14] SLVS-1598 Extract conversion of DTO into a separate class --- .../Rule/RuleInfoConverterTests.cs | 430 ++++++++++++++++ .../Rule/SLCoreRuleMetaDataProviderTests.cs | 479 +----------------- src/Education/Rule/IRuleInfoConverter.cs | 100 ++++ .../Rule/SLCoreRuleMetaDataProvider.cs | 75 +-- 4 files changed, 542 insertions(+), 542 deletions(-) create mode 100644 src/Education.UnitTests/Rule/RuleInfoConverterTests.cs create mode 100644 src/Education/Rule/IRuleInfoConverter.cs diff --git a/src/Education.UnitTests/Rule/RuleInfoConverterTests.cs b/src/Education.UnitTests/Rule/RuleInfoConverterTests.cs new file mode 100644 index 0000000000..a857fe0145 --- /dev/null +++ b/src/Education.UnitTests/Rule/RuleInfoConverterTests.cs @@ -0,0 +1,430 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using SonarLint.VisualStudio.Education.Rule; +using SonarLint.VisualStudio.SLCore.Common.Models; +using SonarLint.VisualStudio.SLCore.Protocol; +using SonarLint.VisualStudio.SLCore.Service.Issue.Models; +using SonarLint.VisualStudio.SLCore.Service.Rules.Models; +using SonarLint.VisualStudio.TestInfrastructure; +using CleanCodeAttribute = SonarLint.VisualStudio.SLCore.Common.Models.CleanCodeAttribute; +using IssueSeverity = SonarLint.VisualStudio.SLCore.Common.Models.IssueSeverity; +using Language = SonarLint.VisualStudio.SLCore.Common.Models.Language; +using SoftwareQuality = SonarLint.VisualStudio.SLCore.Common.Models.SoftwareQuality; +using RuleCleanCodeAttribute = SonarLint.VisualStudio.Core.Analysis.CleanCodeAttribute; +using RuleSoftwareQuality = SonarLint.VisualStudio.Core.Analysis.SoftwareQuality; +using RuleSoftwareQualitySeverity = SonarLint.VisualStudio.Core.Analysis.SoftwareQualitySeverity; + +namespace SonarLint.VisualStudio.Education.UnitTests.Rule; + +[TestClass] +public class RuleInfoConverterTests +{ + private RuleInfoConverter testSubject; + + [TestInitialize] + public void TestInitialize() => testSubject = new RuleInfoConverter(); + + [TestMethod] + public void MefCtor_CheckIsExported() => MefTestHelpers.CheckTypeCanBeImported(); + + [TestMethod] + public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); + + [DataTestMethod] + [DataRow(IssueSeverity.INFO, RuleIssueSeverity.Info)] + [DataRow(IssueSeverity.MAJOR, RuleIssueSeverity.Major)] + [DataRow(IssueSeverity.BLOCKER, RuleIssueSeverity.Blocker)] + [DataRow(IssueSeverity.CRITICAL, RuleIssueSeverity.Critical)] + [DataRow(IssueSeverity.MINOR, RuleIssueSeverity.Minor)] + public void Convert_RuleDetails_CorrectlyConvertsSeverity(IssueSeverity slCore, RuleIssueSeverity expected) + { + var ruleDetails = new EffectiveRuleDetailsDto( + default, + default, + default, + new StandardModeDetails(slCore, default), + default, + default, + default); + + var ruleInfo = testSubject.Convert(ruleDetails); + + ruleInfo.Severity.Should().Be(expected); + } + + [DataTestMethod] + [DataRow(RuleType.CODE_SMELL, RuleIssueType.CodeSmell)] + [DataRow(RuleType.VULNERABILITY, RuleIssueType.Vulnerability)] + [DataRow(RuleType.BUG, RuleIssueType.Bug)] + [DataRow(RuleType.SECURITY_HOTSPOT, RuleIssueType.Hotspot)] + public void Convert_RuleDetails_CorrectlyConvertsType(RuleType slCore, RuleIssueType expected) + { + var ruleDetails = new EffectiveRuleDetailsDto( + default, + default, + default, + new StandardModeDetails(default, slCore), + default, + default, + default); + + var ruleInfo = testSubject.Convert(ruleDetails); + + ruleInfo.IssueType.Should().Be(expected); + } + + [DataTestMethod] + [DataRow(CleanCodeAttribute.CONVENTIONAL, RuleCleanCodeAttribute.Conventional)] + [DataRow(CleanCodeAttribute.FORMATTED, RuleCleanCodeAttribute.Formatted)] + [DataRow(CleanCodeAttribute.IDENTIFIABLE, RuleCleanCodeAttribute.Identifiable)] + [DataRow(CleanCodeAttribute.CLEAR, RuleCleanCodeAttribute.Clear)] + [DataRow(CleanCodeAttribute.COMPLETE, RuleCleanCodeAttribute.Complete)] + [DataRow(CleanCodeAttribute.EFFICIENT, RuleCleanCodeAttribute.Efficient)] + [DataRow(CleanCodeAttribute.LOGICAL, RuleCleanCodeAttribute.Logical)] + [DataRow(CleanCodeAttribute.DISTINCT, RuleCleanCodeAttribute.Distinct)] + [DataRow(CleanCodeAttribute.FOCUSED, RuleCleanCodeAttribute.Focused)] + [DataRow(CleanCodeAttribute.MODULAR, RuleCleanCodeAttribute.Modular)] + [DataRow(CleanCodeAttribute.TESTED, RuleCleanCodeAttribute.Tested)] + [DataRow(CleanCodeAttribute.LAWFUL, RuleCleanCodeAttribute.Lawful)] + [DataRow(CleanCodeAttribute.RESPECTFUL, RuleCleanCodeAttribute.Respectful)] + [DataRow(CleanCodeAttribute.TRUSTWORTHY, RuleCleanCodeAttribute.Trustworthy)] + public void Convert_RuleDetails_CorrectlyConvertsCleanCodeAttribute(CleanCodeAttribute slCore, RuleCleanCodeAttribute expected) + { + var ruleDetails = new EffectiveRuleDetailsDto( + default, + default, + default, + new MQRModeDetails(slCore, default), + default, + default, + default); + + var ruleInfo = testSubject.Convert(ruleDetails); + + ruleInfo.CleanCodeAttribute.Should().Be(expected); + } + + [TestMethod] + public void Convert_RuleDetails_CorrectlyConvertsImpacts() + { + var ruleDetails = new EffectiveRuleDetailsDto( + default, + default, + default, + new MQRModeDetails(default, [ + new ImpactDto(SoftwareQuality.SECURITY, ImpactSeverity.HIGH), + new ImpactDto(SoftwareQuality.RELIABILITY, ImpactSeverity.LOW), + new ImpactDto(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.MEDIUM) + ]), + default, + default, + default); + + var ruleInfo = testSubject.Convert(ruleDetails); + + ruleInfo.DefaultImpacts.Should().BeEquivalentTo(new Dictionary + { + { RuleSoftwareQuality.Security, RuleSoftwareQualitySeverity.High }, + { RuleSoftwareQuality.Reliability, RuleSoftwareQualitySeverity.Low }, + { RuleSoftwareQuality.Maintainability, RuleSoftwareQualitySeverity.Medium } + }); + } + + [TestMethod] + public void Convert_RuleDetails_Standard_SimpleRuleDescription() + { + const string rulekey = "rule:key1"; + var ruleDetails = new EffectiveRuleDetailsDto( + rulekey, + "name", + Language.JS, + new StandardModeDetails(IssueSeverity.CRITICAL, RuleType.VULNERABILITY), + VulnerabilityProbability.MEDIUM, + Either.CreateLeft( + new RuleMonolithicDescriptionDto("content")), + new List()); + + var ruleInfo = testSubject.Convert(ruleDetails); + + ruleInfo.Should().BeEquivalentTo(new RuleInfo(rulekey, + "content", + "name", + RuleIssueSeverity.Critical, + RuleIssueType.Vulnerability, + null, + null, + null)); + } + + [TestMethod] + public void Convert_RuleDetails_MQR_SimpleRuleDescription() + { + const string rulekey = "rule:key1"; + var ruleDetails = new EffectiveRuleDetailsDto( + rulekey, + "name", + Language.JS, + new MQRModeDetails(CleanCodeAttribute.MODULAR, [ + new ImpactDto(SoftwareQuality.SECURITY, ImpactSeverity.HIGH), + new ImpactDto(SoftwareQuality.RELIABILITY, ImpactSeverity.LOW) + ]), + VulnerabilityProbability.MEDIUM, + Either.CreateLeft( + new RuleMonolithicDescriptionDto("content")), + new List()); + + var ruleInfo = testSubject.Convert(ruleDetails); + + ruleInfo.Should().BeEquivalentTo(new RuleInfo(rulekey, + "content", + "name", + null, + null, + null, + RuleCleanCodeAttribute.Modular, + new Dictionary + { + { RuleSoftwareQuality.Security, RuleSoftwareQualitySeverity.High }, { RuleSoftwareQuality.Reliability, RuleSoftwareQualitySeverity.Low } + })); + } + + [TestMethod] + public void Convert_RuleDetails_Standard_RichRuleDescription() + { + const string rulekey = "rule:key1"; + var ruleSplitDescriptionDto = new RuleSplitDescriptionDto("intro", new List()); + var ruleDetails = new EffectiveRuleDetailsDto( + rulekey, + "name", + Language.CPP, + new StandardModeDetails(IssueSeverity.MINOR, RuleType.BUG), + null, + Either.CreateRight(ruleSplitDescriptionDto), + new List { new("ignored", default, default, default) }); + + var ruleInfo = testSubject.Convert(ruleDetails); + + ruleInfo.Should().BeEquivalentTo(new RuleInfo(rulekey, + null, + "name", + RuleIssueSeverity.Minor, + RuleIssueType.Bug, + ruleSplitDescriptionDto, + null, + null)); + } + + [TestMethod] + public void Convert_RuleDetails_MQR_RichRuleDescription() + { + const string rulekey = "rule:key1"; + var ruleSplitDescriptionDto = new RuleSplitDescriptionDto("intro", new List()); + var ruleDetails = new EffectiveRuleDetailsDto( + rulekey, + "name", + Language.CPP, + new MQRModeDetails(CleanCodeAttribute.RESPECTFUL, [ + new ImpactDto(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.MEDIUM) + ]), + null, + Either.CreateRight(ruleSplitDescriptionDto), + new List { new("ignored", default, default, default) }); + + var ruleInfo = testSubject.Convert(ruleDetails); + + ruleInfo.Should().BeEquivalentTo(new RuleInfo(rulekey, + null, + "name", + null, + null, + ruleSplitDescriptionDto, + RuleCleanCodeAttribute.Respectful, + new Dictionary { { RuleSoftwareQuality.Maintainability, RuleSoftwareQualitySeverity.Medium } })); + } + + [DataTestMethod] + [DataRow(IssueSeverity.INFO, RuleIssueSeverity.Info)] + [DataRow(IssueSeverity.MAJOR, RuleIssueSeverity.Major)] + [DataRow(IssueSeverity.BLOCKER, RuleIssueSeverity.Blocker)] + [DataRow(IssueSeverity.CRITICAL, RuleIssueSeverity.Critical)] + [DataRow(IssueSeverity.MINOR, RuleIssueSeverity.Minor)] + public void Convert_IssueDetails_CorrectlyConvertsSeverity(IssueSeverity slCore, RuleIssueSeverity expected) + { + Guid.NewGuid(); + var ruleDetails = CreateEffectiveIssueDetailsDto(new StandardModeDetails(slCore, default)); + + var ruleInfo = testSubject.Convert(ruleDetails); + + ruleInfo.Severity.Should().Be(expected); + } + + [DataTestMethod] + [DataRow(RuleType.CODE_SMELL, RuleIssueType.CodeSmell)] + [DataRow(RuleType.VULNERABILITY, RuleIssueType.Vulnerability)] + [DataRow(RuleType.BUG, RuleIssueType.Bug)] + [DataRow(RuleType.SECURITY_HOTSPOT, RuleIssueType.Hotspot)] + public void Convert_IssueDetails_CorrectlyConvertsType(RuleType slCore, RuleIssueType expected) + { + Guid.NewGuid(); + var ruleDetails = CreateEffectiveIssueDetailsDto(new StandardModeDetails(default, slCore)); + + var ruleInfo = testSubject.Convert(ruleDetails); + + ruleInfo.IssueType.Should().Be(expected); + } + + [DataTestMethod] + [DataRow(CleanCodeAttribute.CONVENTIONAL, RuleCleanCodeAttribute.Conventional)] + [DataRow(CleanCodeAttribute.FORMATTED, RuleCleanCodeAttribute.Formatted)] + [DataRow(CleanCodeAttribute.IDENTIFIABLE, RuleCleanCodeAttribute.Identifiable)] + [DataRow(CleanCodeAttribute.CLEAR, RuleCleanCodeAttribute.Clear)] + [DataRow(CleanCodeAttribute.COMPLETE, RuleCleanCodeAttribute.Complete)] + [DataRow(CleanCodeAttribute.EFFICIENT, RuleCleanCodeAttribute.Efficient)] + [DataRow(CleanCodeAttribute.LOGICAL, RuleCleanCodeAttribute.Logical)] + [DataRow(CleanCodeAttribute.DISTINCT, RuleCleanCodeAttribute.Distinct)] + [DataRow(CleanCodeAttribute.FOCUSED, RuleCleanCodeAttribute.Focused)] + [DataRow(CleanCodeAttribute.MODULAR, RuleCleanCodeAttribute.Modular)] + [DataRow(CleanCodeAttribute.TESTED, RuleCleanCodeAttribute.Tested)] + [DataRow(CleanCodeAttribute.LAWFUL, RuleCleanCodeAttribute.Lawful)] + [DataRow(CleanCodeAttribute.RESPECTFUL, RuleCleanCodeAttribute.Respectful)] + [DataRow(CleanCodeAttribute.TRUSTWORTHY, RuleCleanCodeAttribute.Trustworthy)] + public void Convert_IssueDetails_CorrectlyConvertsCleanCodeAttribute(CleanCodeAttribute slCore, RuleCleanCodeAttribute expected) + { + Guid.NewGuid(); + var ruleDetails = CreateEffectiveIssueDetailsDto(new MQRModeDetails(slCore, default)); + + var ruleInfo = testSubject.Convert(ruleDetails); + + ruleInfo.CleanCodeAttribute.Should().Be(expected); + } + + [TestMethod] + public void Convert_IssueDetails_CorrectlyConvertsImpacts() + { + Guid.NewGuid(); + var ruleDetails = CreateEffectiveIssueDetailsDto(new MQRModeDetails(default, [ + new ImpactDto(SoftwareQuality.SECURITY, ImpactSeverity.HIGH), + new ImpactDto(SoftwareQuality.RELIABILITY, ImpactSeverity.LOW), + new ImpactDto(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.MEDIUM) + ])); + + var ruleInfo = testSubject.Convert(ruleDetails); + + ruleInfo.DefaultImpacts.Should().BeEquivalentTo(new Dictionary + { + { RuleSoftwareQuality.Security, RuleSoftwareQualitySeverity.High }, + { RuleSoftwareQuality.Reliability, RuleSoftwareQualitySeverity.Low }, + { RuleSoftwareQuality.Maintainability, RuleSoftwareQualitySeverity.Medium } + }); + } + + [TestMethod] + public void Convert_IssueDetails_Standard_SimpleRuleDescription() + { + Guid.NewGuid(); + var ruleDetails = CreateEffectiveIssueDetailsDto(new StandardModeDetails(IssueSeverity.CRITICAL, RuleType.VULNERABILITY), + Either.CreateLeft( + new RuleMonolithicDescriptionDto("content"))); + + var ruleInfo = testSubject.Convert(ruleDetails); + + ruleInfo.Should().BeEquivalentTo(new RuleInfo(null, + "content", + null, + RuleIssueSeverity.Critical, + RuleIssueType.Vulnerability, + null, + null, + null)); + } + + [TestMethod] + public void Convert_IssueDetails_MQR_SimpleRuleDescription() + { + Guid.NewGuid(); + var ruleDetails = CreateEffectiveIssueDetailsDto(new MQRModeDetails(CleanCodeAttribute.MODULAR, default), Either.CreateLeft( + new RuleMonolithicDescriptionDto("content"))); + + var ruleInfo = testSubject.Convert(ruleDetails); + + ruleInfo.Should().BeEquivalentTo(new RuleInfo(null, + "content", + null, + null, + null, + null, + RuleCleanCodeAttribute.Modular, + null)); + } + + [TestMethod] + public void Convert_IssueDetails_Standard_RichRuleDescription() + { + Guid.NewGuid(); + var ruleSplitDescriptionDto = new RuleSplitDescriptionDto("intro", new List()); + var ruleDetails = CreateEffectiveIssueDetailsDto(new StandardModeDetails(IssueSeverity.MINOR, RuleType.BUG), + Either.CreateRight(ruleSplitDescriptionDto)); + + var ruleInfo = testSubject.Convert(ruleDetails); + + ruleInfo.Should().BeEquivalentTo(new RuleInfo(null, + null, + null, + RuleIssueSeverity.Minor, + RuleIssueType.Bug, + ruleSplitDescriptionDto, + null, + null)); + } + + [TestMethod] + public void Convert_IssueDetails_MQR_RichRuleDescription() + { + Guid.NewGuid(); + var ruleSplitDescriptionDto = new RuleSplitDescriptionDto("intro", new List()); + var ruleDetails = CreateEffectiveIssueDetailsDto(new MQRModeDetails(CleanCodeAttribute.RESPECTFUL, default), + Either.CreateRight(ruleSplitDescriptionDto)); + + var ruleInfo = testSubject.Convert(ruleDetails); + + ruleInfo.Should().BeEquivalentTo(new RuleInfo(null, + null, + null, + null, + null, + ruleSplitDescriptionDto, + RuleCleanCodeAttribute.Respectful, + null)); + } + + private static EffectiveIssueDetailsDto CreateEffectiveIssueDetailsDto( + Either severityDetails, + Either description = default) => + new( + default, + default, + default, + default, + description, + default, + severityDetails, + default); +} diff --git a/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs b/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs index 0372a27e16..676b139045 100644 --- a/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs +++ b/src/Education.UnitTests/Rule/SLCoreRuleMetaDataProviderTests.cs @@ -21,20 +21,12 @@ using Moq; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Education.Rule; -using SonarLint.VisualStudio.SLCore.Common.Models; using SonarLint.VisualStudio.SLCore.Core; using SonarLint.VisualStudio.SLCore.Protocol; using SonarLint.VisualStudio.SLCore.Service.Rules; using SonarLint.VisualStudio.SLCore.Service.Rules.Models; using SonarLint.VisualStudio.SLCore.State; using SonarLint.VisualStudio.TestInfrastructure; -using CleanCodeAttribute = SonarLint.VisualStudio.SLCore.Common.Models.CleanCodeAttribute; -using IssueSeverity = SonarLint.VisualStudio.SLCore.Common.Models.IssueSeverity; -using Language = SonarLint.VisualStudio.SLCore.Common.Models.Language; -using SoftwareQuality = SonarLint.VisualStudio.SLCore.Common.Models.SoftwareQuality; -using RuleCleanCodeAttribute = SonarLint.VisualStudio.Core.Analysis.CleanCodeAttribute; -using RuleSoftwareQuality = SonarLint.VisualStudio.Core.Analysis.SoftwareQuality; -using RuleSoftwareQualitySeverity = SonarLint.VisualStudio.Core.Analysis.SoftwareQualitySeverity; using SonarLint.VisualStudio.SLCore.Service.Issue; using SonarLint.VisualStudio.SLCore.Service.Issue.Models; @@ -50,277 +42,12 @@ public void MefCtor_CheckIsExported() => MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); [TestMethod] public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); - [DataTestMethod] - [DataRow(IssueSeverity.INFO, RuleIssueSeverity.Info)] - [DataRow(IssueSeverity.MAJOR, RuleIssueSeverity.Major)] - [DataRow(IssueSeverity.BLOCKER, RuleIssueSeverity.Blocker)] - [DataRow(IssueSeverity.CRITICAL, RuleIssueSeverity.Critical)] - [DataRow(IssueSeverity.MINOR, RuleIssueSeverity.Minor)] - public async Task GetRuleInfoAsync_CorrectlyConvertsSeverity(IssueSeverity slCore, RuleIssueSeverity expected) - { - const string rulekey = "rule:key1"; - const string configScopeId = "configscope"; - - var testSubject = - CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _); - SetUpServiceProvider(serviceProviderMock, out var rulesServiceMock); - SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope(configScopeId)); - SetupRulesService(rulesServiceMock, rulekey, configScopeId, new EffectiveRuleDetailsDto( - default, - default, - default, - new StandardModeDetails(slCore, default), - default, - default, - default)); - - var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId); - - ruleInfo.Severity.Should().Be(expected); - } - - [DataTestMethod] - [DataRow(RuleType.CODE_SMELL, RuleIssueType.CodeSmell)] - [DataRow(RuleType.VULNERABILITY, RuleIssueType.Vulnerability)] - [DataRow(RuleType.BUG, RuleIssueType.Bug)] - [DataRow(RuleType.SECURITY_HOTSPOT, RuleIssueType.Hotspot)] - public async Task GetRuleInfoAsync_CorrectlyConvertsType(RuleType slCore, RuleIssueType expected) - { - const string rulekey = "rule:key1"; - const string configScopeId = "configscope"; - - var testSubject = - CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _); - SetUpServiceProvider(serviceProviderMock, out var rulesServiceMock); - SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope(configScopeId)); - SetupRulesService(rulesServiceMock, rulekey, configScopeId, new EffectiveRuleDetailsDto( - default, - default, - default, - new StandardModeDetails(default, slCore), - default, - default, - default)); - - var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId); - - ruleInfo.IssueType.Should().Be(expected); - } - - [DataTestMethod] - [DataRow(CleanCodeAttribute.CONVENTIONAL, RuleCleanCodeAttribute.Conventional)] - [DataRow(CleanCodeAttribute.FORMATTED, RuleCleanCodeAttribute.Formatted)] - [DataRow(CleanCodeAttribute.IDENTIFIABLE, RuleCleanCodeAttribute.Identifiable)] - [DataRow(CleanCodeAttribute.CLEAR, RuleCleanCodeAttribute.Clear)] - [DataRow(CleanCodeAttribute.COMPLETE, RuleCleanCodeAttribute.Complete)] - [DataRow(CleanCodeAttribute.EFFICIENT, RuleCleanCodeAttribute.Efficient)] - [DataRow(CleanCodeAttribute.LOGICAL, RuleCleanCodeAttribute.Logical)] - [DataRow(CleanCodeAttribute.DISTINCT, RuleCleanCodeAttribute.Distinct)] - [DataRow(CleanCodeAttribute.FOCUSED, RuleCleanCodeAttribute.Focused)] - [DataRow(CleanCodeAttribute.MODULAR, RuleCleanCodeAttribute.Modular)] - [DataRow(CleanCodeAttribute.TESTED, RuleCleanCodeAttribute.Tested)] - [DataRow(CleanCodeAttribute.LAWFUL, RuleCleanCodeAttribute.Lawful)] - [DataRow(CleanCodeAttribute.RESPECTFUL, RuleCleanCodeAttribute.Respectful)] - [DataRow(CleanCodeAttribute.TRUSTWORTHY, RuleCleanCodeAttribute.Trustworthy)] - public async Task GetRuleInfoAsync_CorrectlyConvertsCleanCodeAttribute(CleanCodeAttribute slCore, RuleCleanCodeAttribute expected) - { - const string rulekey = "rule:key1"; - const string configScopeId = "configscope"; - - var testSubject = - CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _); - SetUpServiceProvider(serviceProviderMock, out var rulesServiceMock); - SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope(configScopeId)); - SetupRulesService(rulesServiceMock, rulekey, configScopeId, new EffectiveRuleDetailsDto( - default, - default, - default, - new MQRModeDetails(slCore, default), - default, - default, - default)); - - var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId); - - ruleInfo.CleanCodeAttribute.Should().Be(expected); - } - - [TestMethod] - public async Task GetRuleInfoAsync_CorrectlyConvertsImpacts() - { - const string rulekey = "rule:key1"; - const string configScopeId = "configscope"; - - var testSubject = - CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _); - SetUpServiceProvider(serviceProviderMock, out var rulesServiceMock); - SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope(configScopeId)); - SetupRulesService(rulesServiceMock, rulekey, configScopeId, new EffectiveRuleDetailsDto( - default, - default, - default, - new MQRModeDetails(default, [ - new ImpactDto(SoftwareQuality.SECURITY, ImpactSeverity.HIGH), - new ImpactDto(SoftwareQuality.RELIABILITY, ImpactSeverity.LOW), - new ImpactDto(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.MEDIUM) - ]), - default, - default, - default)); - - var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId); - - ruleInfo.DefaultImpacts.Should().BeEquivalentTo(new Dictionary - { - { RuleSoftwareQuality.Security, RuleSoftwareQualitySeverity.High }, - { RuleSoftwareQuality.Reliability, RuleSoftwareQualitySeverity.Low }, - { RuleSoftwareQuality.Maintainability, RuleSoftwareQualitySeverity.Medium } - }); - } - - [TestMethod] - public async Task GetRuleInfoAsync_Standard_SimpleRuleDescription() - { - const string rulekey = "rule:key1"; - const string configScopeId = "configscope"; - - var testSubject = - CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out var logger); - SetUpServiceProvider(serviceProviderMock, out var rulesServiceMock); - SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope(configScopeId)); - SetupRulesService(rulesServiceMock, rulekey, configScopeId, new EffectiveRuleDetailsDto( - rulekey, - "name", - Language.JS, - new StandardModeDetails(IssueSeverity.CRITICAL, RuleType.VULNERABILITY), - VulnerabilityProbability.MEDIUM, - Either.CreateLeft( - new RuleMonolithicDescriptionDto("content")), - new List())); - - var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId); - - ruleInfo.Should().BeEquivalentTo(new RuleInfo(rulekey, - "content", - "name", - RuleIssueSeverity.Critical, - RuleIssueType.Vulnerability, - null, - null, - null)); - } - - [TestMethod] - public async Task GetRuleInfoAsync_MQR_SimpleRuleDescription() - { - const string rulekey = "rule:key1"; - const string configScopeId = "configscope"; - - var testSubject = - CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out var logger); - SetUpServiceProvider(serviceProviderMock, out var rulesServiceMock); - SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope(configScopeId)); - SetupRulesService(rulesServiceMock, rulekey, configScopeId, new EffectiveRuleDetailsDto( - rulekey, - "name", - Language.JS, - new MQRModeDetails(CleanCodeAttribute.MODULAR, [ - new ImpactDto(SoftwareQuality.SECURITY, ImpactSeverity.HIGH), - new ImpactDto(SoftwareQuality.RELIABILITY, ImpactSeverity.LOW) - ]), - VulnerabilityProbability.MEDIUM, - Either.CreateLeft( - new RuleMonolithicDescriptionDto("content")), - new List())); - - var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId); - - ruleInfo.Should().BeEquivalentTo(new RuleInfo(rulekey, - "content", - "name", - null, - null, - null, - RuleCleanCodeAttribute.Modular, - new Dictionary - { - { RuleSoftwareQuality.Security, RuleSoftwareQualitySeverity.High }, { RuleSoftwareQuality.Reliability, RuleSoftwareQualitySeverity.Low } - })); - } - - [TestMethod] - public async Task GetRuleInfoAsync_Standard_RichRuleDescription() - { - const string rulekey = "rule:key1"; - const string configScopeId = "configscope"; - - var testSubject = - CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out var logger); - SetUpServiceProvider(serviceProviderMock, out var rulesServiceMock); - SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope(configScopeId)); - var ruleSplitDescriptionDto = new RuleSplitDescriptionDto("intro", new List()); - SetupRulesService(rulesServiceMock, rulekey, configScopeId, new EffectiveRuleDetailsDto( - rulekey, - "name", - Language.CPP, - new StandardModeDetails(IssueSeverity.MINOR, RuleType.BUG), - null, - Either.CreateRight(ruleSplitDescriptionDto), - new List { new("ignored", default, default, default) })); - - var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId); - - ruleInfo.Should().BeEquivalentTo(new RuleInfo(rulekey, - null, - "name", - RuleIssueSeverity.Minor, - RuleIssueType.Bug, - ruleSplitDescriptionDto, - null, - null)); - logger.AssertNoOutputMessages(); - } - - [TestMethod] - public async Task GetRuleInfoAsync_MQR_RichRuleDescription() - { - const string rulekey = "rule:key1"; - const string configScopeId = "configscope"; - - var testSubject = - CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out var logger); - SetUpServiceProvider(serviceProviderMock, out var rulesServiceMock); - SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope(configScopeId)); - var ruleSplitDescriptionDto = new RuleSplitDescriptionDto("intro", new List()); - SetupRulesService(rulesServiceMock, rulekey, configScopeId, new EffectiveRuleDetailsDto( - rulekey, - "name", - Language.CPP, - new MQRModeDetails(CleanCodeAttribute.RESPECTFUL, [ - new ImpactDto(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.MEDIUM) - ]), - null, - Either.CreateRight(ruleSplitDescriptionDto), - new List { new("ignored", default, default, default) })); - - var ruleInfo = await testSubject.GetRuleInfoAsync(CompositeRuleId); - - ruleInfo.Should().BeEquivalentTo(new RuleInfo(rulekey, - null, - "name", - null, - null, - ruleSplitDescriptionDto, - RuleCleanCodeAttribute.Respectful, - new Dictionary { { RuleSoftwareQuality.Maintainability, RuleSoftwareQualitySeverity.Medium } })); - logger.AssertNoOutputMessages(); - } - [TestMethod] public async Task GetRuleInfoAsync_NoActiveScope_ReturnsNull() { @@ -364,195 +91,6 @@ public void GetRuleInfoAsync_ServiceThrows_ReturnsNullAndLogs() logger.AssertPartialOutputStringExists("my message"); } - [DataTestMethod] - [DataRow(IssueSeverity.INFO, RuleIssueSeverity.Info)] - [DataRow(IssueSeverity.MAJOR, RuleIssueSeverity.Major)] - [DataRow(IssueSeverity.BLOCKER, RuleIssueSeverity.Blocker)] - [DataRow(IssueSeverity.CRITICAL, RuleIssueSeverity.Critical)] - [DataRow(IssueSeverity.MINOR, RuleIssueSeverity.Minor)] - public async Task GetRuleInfoAsync_ForIssue_CorrectlyConvertsSeverity(IssueSeverity slCore, RuleIssueSeverity expected) - { - var issueId = Guid.NewGuid(); - var testSubject = - CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _); - SetUpIssueServiceProvider(serviceProviderMock, out var issuesServiceMock); - SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); - SetupIssuesService(issuesServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new StandardModeDetails(slCore, default))); - - var ruleInfo = await testSubject.GetRuleInfoAsync(default, issueId); - - ruleInfo.Severity.Should().Be(expected); - } - - [DataTestMethod] - [DataRow(RuleType.CODE_SMELL, RuleIssueType.CodeSmell)] - [DataRow(RuleType.VULNERABILITY, RuleIssueType.Vulnerability)] - [DataRow(RuleType.BUG, RuleIssueType.Bug)] - [DataRow(RuleType.SECURITY_HOTSPOT, RuleIssueType.Hotspot)] - public async Task GetRuleInfoAsync_ForIssue_CorrectlyConvertsType(RuleType slCore, RuleIssueType expected) - { - var issueId = Guid.NewGuid(); - var testSubject = - CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _); - SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock); - SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); - SetupIssuesService(issueServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new StandardModeDetails(default, slCore))); - - var ruleInfo = await testSubject.GetRuleInfoAsync(default,issueId); - - ruleInfo.IssueType.Should().Be(expected); - } - - [DataTestMethod] - [DataRow(CleanCodeAttribute.CONVENTIONAL, RuleCleanCodeAttribute.Conventional)] - [DataRow(CleanCodeAttribute.FORMATTED, RuleCleanCodeAttribute.Formatted)] - [DataRow(CleanCodeAttribute.IDENTIFIABLE, RuleCleanCodeAttribute.Identifiable)] - [DataRow(CleanCodeAttribute.CLEAR, RuleCleanCodeAttribute.Clear)] - [DataRow(CleanCodeAttribute.COMPLETE, RuleCleanCodeAttribute.Complete)] - [DataRow(CleanCodeAttribute.EFFICIENT, RuleCleanCodeAttribute.Efficient)] - [DataRow(CleanCodeAttribute.LOGICAL, RuleCleanCodeAttribute.Logical)] - [DataRow(CleanCodeAttribute.DISTINCT, RuleCleanCodeAttribute.Distinct)] - [DataRow(CleanCodeAttribute.FOCUSED, RuleCleanCodeAttribute.Focused)] - [DataRow(CleanCodeAttribute.MODULAR, RuleCleanCodeAttribute.Modular)] - [DataRow(CleanCodeAttribute.TESTED, RuleCleanCodeAttribute.Tested)] - [DataRow(CleanCodeAttribute.LAWFUL, RuleCleanCodeAttribute.Lawful)] - [DataRow(CleanCodeAttribute.RESPECTFUL, RuleCleanCodeAttribute.Respectful)] - [DataRow(CleanCodeAttribute.TRUSTWORTHY, RuleCleanCodeAttribute.Trustworthy)] - public async Task GetRuleInfoAsync_ForIssue_CorrectlyConvertsCleanCodeAttribute(CleanCodeAttribute slCore, RuleCleanCodeAttribute expected) - { - var issueId = Guid.NewGuid(); - var testSubject = - CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _); - SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock); - SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); - SetupIssuesService(issueServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new MQRModeDetails(slCore, default))); - - var ruleInfo = await testSubject.GetRuleInfoAsync(default,issueId); - - ruleInfo.CleanCodeAttribute.Should().Be(expected); - } - - [TestMethod] - public async Task GetRuleInfoAsync_ForIssue_CorrectlyConvertsImpacts() - { - var issueId = Guid.NewGuid(); - var testSubject = - CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _); - SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock); - SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); - SetupIssuesService(issueServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new MQRModeDetails(default, [ - new ImpactDto(SoftwareQuality.SECURITY, ImpactSeverity.HIGH), - new ImpactDto(SoftwareQuality.RELIABILITY, ImpactSeverity.LOW), - new ImpactDto(SoftwareQuality.MAINTAINABILITY, ImpactSeverity.MEDIUM) - ]))); - - var ruleInfo = await testSubject.GetRuleInfoAsync(default,issueId); - - ruleInfo.DefaultImpacts.Should().BeEquivalentTo(new Dictionary - { - { RuleSoftwareQuality.Security, RuleSoftwareQualitySeverity.High }, - { RuleSoftwareQuality.Reliability, RuleSoftwareQualitySeverity.Low }, - { RuleSoftwareQuality.Maintainability, RuleSoftwareQualitySeverity.Medium } - }); - } - - [TestMethod] - public async Task GetRuleInfoAsync_ForIssue_Standard_SimpleRuleDescription() - { - var issueId = Guid.NewGuid(); - var testSubject = - CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _); - SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock); - SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); - SetupIssuesService(issueServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new StandardModeDetails(IssueSeverity.CRITICAL, RuleType.VULNERABILITY), - Either.CreateLeft( - new RuleMonolithicDescriptionDto("content")))); - - var ruleInfo = await testSubject.GetRuleInfoAsync(default,issueId); - - ruleInfo.Should().BeEquivalentTo(new RuleInfo(null, - "content", - null, - RuleIssueSeverity.Critical, - RuleIssueType.Vulnerability, - null, - null, - null)); - } - - [TestMethod] - public async Task GetRuleInfoAsync_ForIssue_MQR_SimpleRuleDescription() - { - var issueId = Guid.NewGuid(); - var testSubject = - CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out _); - SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock); - SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); - SetupIssuesService(issueServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new MQRModeDetails(CleanCodeAttribute.MODULAR, default), Either.CreateLeft( - new RuleMonolithicDescriptionDto("content")))); - - var ruleInfo = await testSubject.GetRuleInfoAsync(default,issueId); - - ruleInfo.Should().BeEquivalentTo(new RuleInfo(null, - "content", - null, - null, - null, - null, - RuleCleanCodeAttribute.Modular, - null)); - } - - [TestMethod] - public async Task GetRuleInfoAsync_ForIssue_Standard_RichRuleDescription() - { - var issueId = Guid.NewGuid(); - var testSubject = - CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out var logger); - SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock); - SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); - var ruleSplitDescriptionDto = new RuleSplitDescriptionDto("intro", new List()); - SetupIssuesService(issueServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new StandardModeDetails(IssueSeverity.MINOR, RuleType.BUG), - Either.CreateRight(ruleSplitDescriptionDto))); - - var ruleInfo = await testSubject.GetRuleInfoAsync(default,issueId); - - ruleInfo.Should().BeEquivalentTo(new RuleInfo(null, - null, - null, - RuleIssueSeverity.Minor, - RuleIssueType.Bug, - ruleSplitDescriptionDto, - null, - null)); - logger.AssertNoOutputMessages(); - } - - [TestMethod] - public async Task GetRuleInfoAsync_ForIssue_MQR_RichRuleDescription() - { - var issueId = Guid.NewGuid(); - var testSubject = - CreateTestSubject(out var serviceProviderMock, out var configScopeTrackerMock, out var logger); - SetUpIssueServiceProvider(serviceProviderMock, out var issueServiceMock); - SetUpConfigScopeTracker(configScopeTrackerMock, new ConfigurationScope("configscope")); - var ruleSplitDescriptionDto = new RuleSplitDescriptionDto("intro", new List()); - SetupIssuesService(issueServiceMock, issueId, "configscope", CreateEffectiveIssueDetailsDto(new MQRModeDetails(CleanCodeAttribute.RESPECTFUL, default), - Either.CreateRight(ruleSplitDescriptionDto))); - - var ruleInfo = await testSubject.GetRuleInfoAsync(default,issueId); - - ruleInfo.Should().BeEquivalentTo(new RuleInfo(null, - null, - null, - null, - null, - ruleSplitDescriptionDto, - RuleCleanCodeAttribute.Respectful, - null)); - logger.AssertNoOutputMessages(); - } - [TestMethod] public async Task GetRuleInfoAsync_ForIssue_NoActiveScope_ReturnsNull() { @@ -692,16 +230,6 @@ private static void SetUpConfigScopeTracker( ConfigurationScope scope) => configScopeTrackerMock.SetupGet(x => x.Current).Returns(scope); - private static void SetupRulesService( - Mock rulesServiceMock, - string rulekey, - string configScopeId, - EffectiveRuleDetailsDto response) => - rulesServiceMock - .Setup(r => r.GetEffectiveRuleDetailsAsync(It.Is(p => - p.ruleKey == rulekey && p.configurationScopeId == configScopeId))) - .ReturnsAsync(new GetEffectiveRuleDetailsResponse(response)); - private static void SetupIssuesService( Mock issuesServiceMock, Guid id, @@ -736,8 +264,11 @@ private static SLCoreRuleMetaDataProvider CreateTestSubject( { serviceProviderMock = new Mock(); configScopeTrackerMock = new Mock(); + configScopeTrackerMock = new Mock(); + var ruleInfoConverter = new Mock(); + ruleInfoConverter.Setup(x => x.Convert(It.IsAny())).Returns(new RuleInfo(default, default, default, default, default, default, default, default)); logger = new TestLogger(); - return new SLCoreRuleMetaDataProvider(serviceProviderMock.Object, configScopeTrackerMock.Object, logger); + return new SLCoreRuleMetaDataProvider(serviceProviderMock.Object, configScopeTrackerMock.Object, ruleInfoConverter.Object, logger); } private static EffectiveIssueDetailsDto CreateEffectiveIssueDetailsDto(Either severityDetails, diff --git a/src/Education/Rule/IRuleInfoConverter.cs b/src/Education/Rule/IRuleInfoConverter.cs new file mode 100644 index 0000000000..39c4571258 --- /dev/null +++ b/src/Education/Rule/IRuleInfoConverter.cs @@ -0,0 +1,100 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using System.ComponentModel.Composition; +using SonarLint.VisualStudio.Core.Analysis; +using SonarLint.VisualStudio.SLCore.Common.Helpers; +using SonarLint.VisualStudio.SLCore.Common.Models; +using SonarLint.VisualStudio.SLCore.Service.Rules.Models; +using CleanCodeAttribute = SonarLint.VisualStudio.Core.Analysis.CleanCodeAttribute; +using IssueSeverity = SonarLint.VisualStudio.SLCore.Common.Models.IssueSeverity; +using SoftwareQuality = SonarLint.VisualStudio.Core.Analysis.SoftwareQuality; + +namespace SonarLint.VisualStudio.Education.Rule; + +internal interface IRuleInfoConverter +{ + IRuleInfo Convert(IRuleDetails details); +} + +[Export(typeof(IRuleInfoConverter))] +[PartCreationPolicy(CreationPolicy.Shared)] +public class RuleInfoConverter : IRuleInfoConverter +{ + [ImportingConstructor] + public RuleInfoConverter() { } + + public IRuleInfo Convert(IRuleDetails details) => + new RuleInfo(details.key, + HtmlXmlCompatibilityHelper.EnsureHtmlIsXml(details.description?.Left?.htmlContent), + details.name, + Convert(details.severityDetails.Left?.severity), + Convert(details.severityDetails.Left?.type), + details.description?.Right, + Convert(details.severityDetails.Right?.cleanCodeAttribute), + Convert(details.severityDetails.Right?.impacts)); + + private static RuleIssueSeverity? Convert(IssueSeverity? issueSeverity) => + issueSeverity switch + { + IssueSeverity.BLOCKER => RuleIssueSeverity.Blocker, + IssueSeverity.CRITICAL => RuleIssueSeverity.Critical, + IssueSeverity.MAJOR => RuleIssueSeverity.Major, + IssueSeverity.MINOR => RuleIssueSeverity.Minor, + IssueSeverity.INFO => RuleIssueSeverity.Info, + null => null, + _ => throw new ArgumentOutOfRangeException(nameof(issueSeverity), issueSeverity, null) + }; + + private static RuleIssueType? Convert(RuleType? ruleType) => + ruleType switch + { + RuleType.CODE_SMELL => RuleIssueType.CodeSmell, + RuleType.BUG => RuleIssueType.Bug, + RuleType.VULNERABILITY => RuleIssueType.Vulnerability, + RuleType.SECURITY_HOTSPOT => RuleIssueType.Hotspot, + null => null, + _ => throw new ArgumentOutOfRangeException(nameof(ruleType), ruleType, null) + }; + + private static CleanCodeAttribute? Convert(SLCore.Common.Models.CleanCodeAttribute? cleanCodeAttribute) => + cleanCodeAttribute switch + { + SLCore.Common.Models.CleanCodeAttribute.CONVENTIONAL => CleanCodeAttribute.Conventional, + SLCore.Common.Models.CleanCodeAttribute.FORMATTED => CleanCodeAttribute.Formatted, + SLCore.Common.Models.CleanCodeAttribute.IDENTIFIABLE => CleanCodeAttribute.Identifiable, + SLCore.Common.Models.CleanCodeAttribute.CLEAR => CleanCodeAttribute.Clear, + SLCore.Common.Models.CleanCodeAttribute.COMPLETE => CleanCodeAttribute.Complete, + SLCore.Common.Models.CleanCodeAttribute.EFFICIENT => CleanCodeAttribute.Efficient, + SLCore.Common.Models.CleanCodeAttribute.LOGICAL => CleanCodeAttribute.Logical, + SLCore.Common.Models.CleanCodeAttribute.DISTINCT => CleanCodeAttribute.Distinct, + SLCore.Common.Models.CleanCodeAttribute.FOCUSED => CleanCodeAttribute.Focused, + SLCore.Common.Models.CleanCodeAttribute.MODULAR => CleanCodeAttribute.Modular, + SLCore.Common.Models.CleanCodeAttribute.TESTED => CleanCodeAttribute.Tested, + SLCore.Common.Models.CleanCodeAttribute.LAWFUL => CleanCodeAttribute.Lawful, + SLCore.Common.Models.CleanCodeAttribute.RESPECTFUL => CleanCodeAttribute.Respectful, + SLCore.Common.Models.CleanCodeAttribute.TRUSTWORTHY => CleanCodeAttribute.Trustworthy, + null => null, + _ => throw new ArgumentOutOfRangeException(nameof(cleanCodeAttribute), cleanCodeAttribute, null) + }; + + private static Dictionary Convert(List cleanCodeAttribute) => + cleanCodeAttribute?.ToDictionary(x => x.softwareQuality.ToSoftwareQuality(), x => x.impactSeverity.ToSoftwareQualitySeverity()); +} diff --git a/src/Education/Rule/SLCoreRuleMetaDataProvider.cs b/src/Education/Rule/SLCoreRuleMetaDataProvider.cs index 511f26ec33..987f7d943d 100644 --- a/src/Education/Rule/SLCoreRuleMetaDataProvider.cs +++ b/src/Education/Rule/SLCoreRuleMetaDataProvider.cs @@ -20,17 +20,10 @@ using System.ComponentModel.Composition; using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Analysis; -using SonarLint.VisualStudio.SLCore.Common.Helpers; -using SonarLint.VisualStudio.SLCore.Common.Models; using SonarLint.VisualStudio.SLCore.Core; using SonarLint.VisualStudio.SLCore.Service.Issue; using SonarLint.VisualStudio.SLCore.Service.Rules; -using SonarLint.VisualStudio.SLCore.Service.Rules.Models; using SonarLint.VisualStudio.SLCore.State; -using CleanCodeAttribute = SonarLint.VisualStudio.Core.Analysis.CleanCodeAttribute; -using IssueSeverity = SonarLint.VisualStudio.SLCore.Common.Models.IssueSeverity; -using SoftwareQuality = SonarLint.VisualStudio.Core.Analysis.SoftwareQuality; namespace SonarLint.VisualStudio.Education.Rule; @@ -39,15 +32,19 @@ namespace SonarLint.VisualStudio.Education.Rule; internal class SLCoreRuleMetaDataProvider : IRuleMetaDataProvider { private readonly IActiveConfigScopeTracker activeConfigScopeTracker; + private readonly IRuleInfoConverter ruleInfoConverter; private readonly ILogger logger; private readonly ISLCoreServiceProvider slCoreServiceProvider; [ImportingConstructor] public SLCoreRuleMetaDataProvider(ISLCoreServiceProvider slCoreServiceProvider, - IActiveConfigScopeTracker activeConfigScopeTracker, ILogger logger) + IActiveConfigScopeTracker activeConfigScopeTracker, + IRuleInfoConverter ruleInfoConverter, + ILogger logger) { this.slCoreServiceProvider = slCoreServiceProvider; this.activeConfigScopeTracker = activeConfigScopeTracker; + this.ruleInfoConverter = ruleInfoConverter; this.logger = logger; } @@ -68,7 +65,7 @@ private async Task GetEffectiveIssueDetailsAsync(Guid issueId) { var issueDetailsResponse = await rulesRpcService.GetEffectiveIssueDetailsAsync( new GetEffectiveIssueDetailsParams(configurationScopeId, issueId)); - return Convert(issueDetailsResponse.details); + return ruleInfoConverter.Convert(issueDetailsResponse.details); } catch (Exception e) { @@ -88,7 +85,7 @@ private async Task GetEffectiveRuleDetailsAsync(SonarCompositeRuleId { var ruleDetailsResponse = await rulesRpcService.GetEffectiveRuleDetailsAsync( new GetEffectiveRuleDetailsParams(configurationScopeId, ruleId.ToString())); - return Convert(ruleDetailsResponse.details); + return ruleInfoConverter.Convert(ruleDetailsResponse.details); } catch (Exception e) { @@ -98,62 +95,4 @@ private async Task GetEffectiveRuleDetailsAsync(SonarCompositeRuleId return null; } - - private static RuleInfo Convert(IRuleDetails effectiveRuleDetailsAsync) => - new(effectiveRuleDetailsAsync.key, - HtmlXmlCompatibilityHelper.EnsureHtmlIsXml(effectiveRuleDetailsAsync.description?.Left?.htmlContent), - effectiveRuleDetailsAsync.name, - Convert(effectiveRuleDetailsAsync.severityDetails.Left?.severity), - Convert(effectiveRuleDetailsAsync.severityDetails.Left?.type), - effectiveRuleDetailsAsync.description?.Right, - Convert(effectiveRuleDetailsAsync.severityDetails.Right?.cleanCodeAttribute), - Convert(effectiveRuleDetailsAsync.severityDetails.Right?.impacts)); - - private static Dictionary Convert(List cleanCodeAttribute) => - cleanCodeAttribute?.ToDictionary(x => x.softwareQuality.ToSoftwareQuality(), x => x.impactSeverity.ToSoftwareQualitySeverity()); - - - private static RuleIssueSeverity? Convert(IssueSeverity? issueSeverity) => - issueSeverity switch - { - IssueSeverity.BLOCKER => RuleIssueSeverity.Blocker, - IssueSeverity.CRITICAL => RuleIssueSeverity.Critical, - IssueSeverity.MAJOR => RuleIssueSeverity.Major, - IssueSeverity.MINOR => RuleIssueSeverity.Minor, - IssueSeverity.INFO => RuleIssueSeverity.Info, - null => null, - _ => throw new ArgumentOutOfRangeException(nameof(issueSeverity), issueSeverity, null) - }; - - private static RuleIssueType? Convert(RuleType? ruleType) => - ruleType switch - { - RuleType.CODE_SMELL => RuleIssueType.CodeSmell, - RuleType.BUG => RuleIssueType.Bug, - RuleType.VULNERABILITY => RuleIssueType.Vulnerability, - RuleType.SECURITY_HOTSPOT => RuleIssueType.Hotspot, - null => null, - _ => throw new ArgumentOutOfRangeException(nameof(ruleType), ruleType, null) - }; - - private static CleanCodeAttribute? Convert(SLCore.Common.Models.CleanCodeAttribute? cleanCodeAttribute) => - cleanCodeAttribute switch - { - SLCore.Common.Models.CleanCodeAttribute.CONVENTIONAL => CleanCodeAttribute.Conventional, - SLCore.Common.Models.CleanCodeAttribute.FORMATTED => CleanCodeAttribute.Formatted, - SLCore.Common.Models.CleanCodeAttribute.IDENTIFIABLE => CleanCodeAttribute.Identifiable, - SLCore.Common.Models.CleanCodeAttribute.CLEAR => CleanCodeAttribute.Clear, - SLCore.Common.Models.CleanCodeAttribute.COMPLETE => CleanCodeAttribute.Complete, - SLCore.Common.Models.CleanCodeAttribute.EFFICIENT => CleanCodeAttribute.Efficient, - SLCore.Common.Models.CleanCodeAttribute.LOGICAL => CleanCodeAttribute.Logical, - SLCore.Common.Models.CleanCodeAttribute.DISTINCT => CleanCodeAttribute.Distinct, - SLCore.Common.Models.CleanCodeAttribute.FOCUSED => CleanCodeAttribute.Focused, - SLCore.Common.Models.CleanCodeAttribute.MODULAR => CleanCodeAttribute.Modular, - SLCore.Common.Models.CleanCodeAttribute.TESTED => CleanCodeAttribute.Tested, - SLCore.Common.Models.CleanCodeAttribute.LAWFUL => CleanCodeAttribute.Lawful, - SLCore.Common.Models.CleanCodeAttribute.RESPECTFUL => CleanCodeAttribute.Respectful, - SLCore.Common.Models.CleanCodeAttribute.TRUSTWORTHY => CleanCodeAttribute.Trustworthy, - null => null, - _ => throw new ArgumentOutOfRangeException(nameof(cleanCodeAttribute), cleanCodeAttribute, null) - }; } From cd696eef15cba2e729fe21ee53156d35910ebc67 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Thu, 14 Nov 2024 11:26:20 +0100 Subject: [PATCH 13/14] SLVS-1598 Move up check --- .../Rule/SLCoreRuleMetaDataProvider.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Education/Rule/SLCoreRuleMetaDataProvider.cs b/src/Education/Rule/SLCoreRuleMetaDataProvider.cs index 987f7d943d..8986f656e9 100644 --- a/src/Education/Rule/SLCoreRuleMetaDataProvider.cs +++ b/src/Education/Rule/SLCoreRuleMetaDataProvider.cs @@ -51,15 +51,19 @@ public SLCoreRuleMetaDataProvider(ISLCoreServiceProvider slCoreServiceProvider, /// public async Task GetRuleInfoAsync(SonarCompositeRuleId ruleId, Guid? issueId = null) { - var ruleInfoFromIssue = issueId != null ? await GetEffectiveIssueDetailsAsync(issueId.Value) : null; + if (activeConfigScopeTracker.Current is not { Id: var configurationScopeId }) + { + return null; + } + + var ruleInfoFromIssue = issueId != null ? await GetEffectiveIssueDetailsAsync(configurationScopeId, issueId.Value) : null; - return ruleInfoFromIssue ?? await GetEffectiveRuleDetailsAsync(ruleId); + return ruleInfoFromIssue ?? await GetEffectiveRuleDetailsAsync(configurationScopeId, ruleId); } - private async Task GetEffectiveIssueDetailsAsync(Guid issueId) + private async Task GetEffectiveIssueDetailsAsync(string configurationScopeId, Guid issueId) { - if (activeConfigScopeTracker.Current is { Id: var configurationScopeId } - && slCoreServiceProvider.TryGetTransientService(out IIssueSLCoreService rulesRpcService)) + if (slCoreServiceProvider.TryGetTransientService(out IIssueSLCoreService rulesRpcService)) { try { @@ -76,10 +80,9 @@ private async Task GetEffectiveIssueDetailsAsync(Guid issueId) return null; } - private async Task GetEffectiveRuleDetailsAsync(SonarCompositeRuleId ruleId) + private async Task GetEffectiveRuleDetailsAsync(string configurationScopeId, SonarCompositeRuleId ruleId) { - if (activeConfigScopeTracker.Current is { Id: var configurationScopeId } - && slCoreServiceProvider.TryGetTransientService(out IRulesSLCoreService rulesRpcService)) + if (slCoreServiceProvider.TryGetTransientService(out IRulesSLCoreService rulesRpcService)) { try { From 17d9af12920a66d75c18932034bb18d6dd0ce650 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Thu, 14 Nov 2024 11:43:58 +0100 Subject: [PATCH 14/14] SLVS-1598 Fix compilation error in integration tests --- .../RuleDescriptionConversionSmokeTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SLCore.IntegrationTests/RuleDescriptionConversionSmokeTest.cs b/src/SLCore.IntegrationTests/RuleDescriptionConversionSmokeTest.cs index 5568601d39..bdfb64a2e7 100644 --- a/src/SLCore.IntegrationTests/RuleDescriptionConversionSmokeTest.cs +++ b/src/SLCore.IntegrationTests/RuleDescriptionConversionSmokeTest.cs @@ -132,6 +132,7 @@ private static SLCoreRuleMetaDataProvider CreateSlCoreRuleMetaDataProvider(SLCor IActiveConfigScopeTracker activeConfigScopeTracker, ILogger testLogger) => new(slCoreTestRunner.SLCoreServiceProvider, activeConfigScopeTracker, + new RuleInfoConverter(), testLogger); private static ActiveConfigScopeTracker CreateActiveConfigScopeTracker(SLCoreTestRunner slCoreTestRunner) =>