From 620cffed553a838732f80d7a4e3f247b27fc5587 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Thu, 9 Jan 2025 14:31:25 +0100 Subject: [PATCH] SLVS-1641 Add support of MQR mode to GlobalConfigGenerator --- .../Binding/GlobalConfigGeneratorTests.cs | 295 +++++++++++------- .../Binding/GlobalConfigGenerator.cs | 139 ++++----- 2 files changed, 248 insertions(+), 186 deletions(-) diff --git a/src/ConnectedMode.UnitTests/Binding/GlobalConfigGeneratorTests.cs b/src/ConnectedMode.UnitTests/Binding/GlobalConfigGeneratorTests.cs index 8523adfe8..657ca2768 100644 --- a/src/ConnectedMode.UnitTests/Binding/GlobalConfigGeneratorTests.cs +++ b/src/ConnectedMode.UnitTests/Binding/GlobalConfigGeneratorTests.cs @@ -18,124 +18,195 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; using System.Text; using SonarLint.VisualStudio.ConnectedMode.Binding; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.CSharpVB; using SonarQube.Client.Models; -namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Binding +namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Binding; + +[TestClass] +public class GlobalConfigGeneratorTests { - [TestClass] - public class GlobalConfigGeneratorTests - { - [TestMethod] - public void Generate_RulesAreNull_ThrowsNullException() - { - var generator = new GlobalConfigGenerator(); - - Action act = () => generator.Generate(null); - act.Should().ThrowExactly().And.ParamName.Should().Be("rules"); - } - - [TestMethod] - public void Generate_MultipleRules_RulesAreSorted() - { - var generator = new GlobalConfigGenerator(); - var rules = new List() - { - CreateRule("3", ""), - CreateRule("5", ""), - CreateRule("2", ""), - CreateRule("8", ""), - }; - - var result = generator.Generate(rules); - - var sb = new StringBuilder(); - sb.AppendLine("is_global=true"); - sb.AppendLine("global_level=1999999999"); - sb.AppendLine(GetRuleString("2", "suggestion")); - sb.AppendLine(GetRuleString("3", "suggestion")); - sb.AppendLine(GetRuleString("5", "suggestion")); - sb.AppendLine(GetRuleString("8", "suggestion")); - - result.Should().Be(sb.ToString()); - } - - [TestMethod] - public void Generate_GlobalConfigSettingsAreCorrect() - { - var generator = new GlobalConfigGenerator(); - var rules = new List() { }; - - var result = generator.Generate(rules); - - var sb = new StringBuilder(); - sb.AppendLine("is_global=true"); - sb.AppendLine("global_level=1999999999"); - - result.Should().Be(sb.ToString()); - } - - [TestMethod] - [DataRow(RuleAction.Info, "suggestion")] - [DataRow(RuleAction.Warning, "warning")] - [DataRow(RuleAction.None, "none")] - [DataRow(RuleAction.Error, "error")] - [DataRow(RuleAction.Hidden, "silent")] - public void GetActionText_Valid(RuleAction action, string expected) - { - GlobalConfigGenerator.GetActionText(action).Should().Be(expected); - } - - [TestMethod] - public void GetActionText_Invalid() - { - Action act = () => GlobalConfigGenerator.GetActionText((RuleAction)(-1)); - act.Should().Throw(); - } - - [TestMethod] - [DataRow(SonarQubeIssueSeverity.Info, RuleAction.Info)] - [DataRow(SonarQubeIssueSeverity.Minor, RuleAction.Info)] - [DataRow(SonarQubeIssueSeverity.Major, RuleAction.Warning)] - [DataRow(SonarQubeIssueSeverity.Critical, RuleAction.Warning)] - public void GetVSSeverity_NotBlocker_CorrectlyMapped(SonarQubeIssueSeverity sqSeverity, RuleAction expectedVsSeverity) - { - var testSubject = new GlobalConfigGenerator(); - - testSubject.GetVsSeverity(sqSeverity).Should().Be(expectedVsSeverity); - } - - [TestMethod] - [DataRow(true, RuleAction.Error)] - [DataRow(false, RuleAction.Warning)] - public void GetVSSeverity_Blocker_CorrectlyMapped(bool shouldTreatBlockerAsError, RuleAction expectedVsSeverity) - { - var envSettingsMock = new Mock(); - envSettingsMock.Setup(x => x.TreatBlockerSeverityAsError()).Returns(shouldTreatBlockerAsError); - - var testSubject = new GlobalConfigGenerator(envSettingsMock.Object); - - testSubject.GetVsSeverity(SonarQubeIssueSeverity.Blocker).Should().Be(expectedVsSeverity); - } - - [TestMethod] - [DataRow(SonarQubeIssueSeverity.Unknown)] - [DataRow((SonarQubeIssueSeverity)(-1))] - public void GetVSSeverity_Invalid_Throws(SonarQubeIssueSeverity sqSeverity) - { - Action act = () => new GlobalConfigGenerator().GetVsSeverity(sqSeverity); - act.Should().Throw(); - } - - private static SonarQubeRule CreateRule(string ruleKey, string repoKey, bool isActive = true) => - new SonarQubeRule(ruleKey, repoKey, isActive, SonarQubeIssueSeverity.Info, null, null, new Dictionary(), SonarQubeIssueType.Unknown); - - private string GetRuleString(string expectedKey, string expectedSeverity) => - $"dotnet_diagnostic.{expectedKey}.severity = {expectedSeverity}"; + private GlobalConfigGenerator testSubject; + private IEnvironmentSettings environmentSettings; + + [TestInitialize] + public void TestInitialize() + { + environmentSettings = Substitute.For(); + testSubject = new GlobalConfigGenerator(environmentSettings); + } + + [TestMethod] + public void Generate_RulesAreNull_ThrowsNullException() + { + Action act = () => testSubject.Generate(null); + act.Should().ThrowExactly().And.ParamName.Should().Be("rules"); + } + + [TestMethod] + public void Generate_MultipleRules_RulesAreSorted() + { + List rules = + [ + CreateRule("3", ""), CreateRule("5", ""), CreateRule("2", ""), CreateRule("8", "") + ]; + + var result = testSubject.Generate(rules); + + var sb = new StringBuilder(); + sb.AppendLine("is_global=true"); + sb.AppendLine("global_level=1999999999"); + sb.AppendLine(GetRuleString("2", "suggestion")); + sb.AppendLine(GetRuleString("3", "suggestion")); + sb.AppendLine(GetRuleString("5", "suggestion")); + sb.AppendLine(GetRuleString("8", "suggestion")); + + result.Should().Be(sb.ToString()); + } + + [TestMethod] + public void Generate_GlobalConfigSettingsAreCorrect() + { + var rules = new List() { }; + + var result = testSubject.Generate(rules); + + var sb = new StringBuilder(); + sb.AppendLine("is_global=true"); + sb.AppendLine("global_level=1999999999"); + + result.Should().Be(sb.ToString()); } + + [TestMethod] + [DataRow(RuleAction.Info, "suggestion")] + [DataRow(RuleAction.Warning, "warning")] + [DataRow(RuleAction.None, "none")] + [DataRow(RuleAction.Error, "error")] + [DataRow(RuleAction.Hidden, "silent")] + public void GetActionText_Valid(RuleAction action, string expected) + { + GlobalConfigGenerator.GetActionText(action).Should().Be(expected); + } + + [TestMethod] + public void GetActionText_Invalid() + { + Action act = () => GlobalConfigGenerator.GetActionText((RuleAction)(-1)); + act.Should().Throw(); + } + + [TestMethod] + [DataRow(SonarQubeIssueSeverity.Info, RuleAction.Info)] + [DataRow(SonarQubeIssueSeverity.Minor, RuleAction.Info)] + [DataRow(SonarQubeIssueSeverity.Major, RuleAction.Warning)] + [DataRow(SonarQubeIssueSeverity.Critical, RuleAction.Warning)] + public void GetVSSeverity_NotBlocker_CorrectlyMapped(SonarQubeIssueSeverity sqSeverity, RuleAction expectedVsSeverity) + { + testSubject.GetVsSeverity(sqSeverity).Should().Be(expectedVsSeverity); + } + + [TestMethod] + [DataRow(true, RuleAction.Error)] + [DataRow(false, RuleAction.Warning)] + public void GetVSSeverity_Blocker_CorrectlyMapped(bool shouldTreatBlockerAsError, RuleAction expectedVsSeverity) + { + environmentSettings.TreatBlockerSeverityAsError().Returns(shouldTreatBlockerAsError); + + testSubject.GetVsSeverity(SonarQubeIssueSeverity.Blocker).Should().Be(expectedVsSeverity); + } + + [TestMethod] + [DataRow(SonarQubeIssueSeverity.Unknown)] + [DataRow((SonarQubeIssueSeverity)(-1))] + public void GetVSSeverity_Invalid_Throws(SonarQubeIssueSeverity sqSeverity) + { + Action act = () => testSubject.GetVsSeverity(sqSeverity); + act.Should().Throw(); + } + + [DataTestMethod] + [DataRow(SonarQubeSoftwareQualitySeverity.Info, RuleAction.Info)] + [DataRow(SonarQubeSoftwareQualitySeverity.Low, RuleAction.Info)] + [DataRow(SonarQubeSoftwareQualitySeverity.Medium, RuleAction.Warning)] + public void GetVSSeverity_FromSoftwareQualitySeverity_NotBlocker_CorrectlyMapped(SonarQubeSoftwareQualitySeverity sqSeverity, RuleAction expectedVsSeverity) + { + testSubject.GetVsSeverity([sqSeverity]).Should().Be(expectedVsSeverity); + } + + [TestMethod] + [DataRow(SonarQubeSoftwareQualitySeverity.High, true, RuleAction.Error)] + [DataRow(SonarQubeSoftwareQualitySeverity.High, false, RuleAction.Warning)] + [DataRow(SonarQubeSoftwareQualitySeverity.Blocker, true, RuleAction.Error)] + [DataRow(SonarQubeSoftwareQualitySeverity.Blocker, false, RuleAction.Warning)] + public void GetVSSeverity_FromSoftwareQualitySeverity_Blocker_CorrectlyMapped(SonarQubeSoftwareQualitySeverity sqSeverity, bool shouldTreatBlockerAsError, RuleAction expectedVsSeverity) + { + environmentSettings.TreatBlockerSeverityAsError().Returns(shouldTreatBlockerAsError); + + testSubject.GetVsSeverity([sqSeverity]).Should().Be(expectedVsSeverity); + } + + [TestMethod] + public void GetVSSeverity_FromSoftwareQualitySeverity_Invalid_Throws() + { + Action act = () => testSubject.GetVsSeverity([(SonarQubeSoftwareQualitySeverity)(-1)]); + act.Should().Throw(); + } + + [TestMethod] + public void GetVSSeverity_FromSoftwareQualitySeverity_Multiple_TakesHighest() + { + testSubject.GetVsSeverity([SonarQubeSoftwareQualitySeverity.Blocker, SonarQubeSoftwareQualitySeverity.Low]).Should().Be(RuleAction.Warning); + testSubject.GetVsSeverity([SonarQubeSoftwareQualitySeverity.Low, SonarQubeSoftwareQualitySeverity.Blocker]).Should().Be(RuleAction.Warning); + testSubject.GetVsSeverity([SonarQubeSoftwareQualitySeverity.Blocker, SonarQubeSoftwareQualitySeverity.Blocker]).Should().Be(RuleAction.Warning); + testSubject.GetVsSeverity([SonarQubeSoftwareQualitySeverity.Low, SonarQubeSoftwareQualitySeverity.Info]).Should().Be(RuleAction.Info); + testSubject.GetVsSeverity([SonarQubeSoftwareQualitySeverity.Low, SonarQubeSoftwareQualitySeverity.Info, SonarQubeSoftwareQualitySeverity.High]).Should().Be(RuleAction.Warning); + } + + [TestMethod] + public void GetVSSeverity_FromSoftwareQualitySeverity_Null_ReturnsNull() + { + testSubject.GetVsSeverity((ICollection)null).Should().BeNull(); + } + + [TestMethod] + public void GetVSSeverity_FromSoftwareQualitySeverity_Empty_ReturnsNull() + { + testSubject.GetVsSeverity([]).Should().BeNull(); + } + + [TestMethod] + public void GetVsSeverity_FromRule_MQRPresent_PrefersMQR() + { + var ruleWithMqr = CreateRule(new() { { SonarQubeSoftwareQuality.Maintainability, SonarQubeSoftwareQualitySeverity.Low } }, SonarQubeIssueSeverity.Blocker); + + testSubject.GetVsSeverity(ruleWithMqr).Should().Be(RuleAction.Info); + } + + [TestMethod] + public void GetVsSeverity_FromRule_MQRNotPresent_UsesStandard() + { + var ruleWithMqr = CreateRule(null, SonarQubeIssueSeverity.Blocker); + + testSubject.GetVsSeverity(ruleWithMqr).Should().Be(RuleAction.Warning); + } + + private static SonarQubeRule CreateRule(Dictionary mqrSeverity, SonarQubeIssueSeverity severity) => + new(default, + default, + default, + severity, + default, + mqrSeverity, + default, + default); + + private static SonarQubeRule CreateRule(string ruleKey, string repoKey, bool isActive = true) => + new SonarQubeRule(ruleKey, repoKey, isActive, SonarQubeIssueSeverity.Info, null, null, new Dictionary(), SonarQubeIssueType.Unknown); + + private string GetRuleString(string expectedKey, string expectedSeverity) => + $"dotnet_diagnostic.{expectedKey}.severity = {expectedSeverity}"; } diff --git a/src/ConnectedMode/Binding/GlobalConfigGenerator.cs b/src/ConnectedMode/Binding/GlobalConfigGenerator.cs index 40c5004a1..d2db479bb 100644 --- a/src/ConnectedMode/Binding/GlobalConfigGenerator.cs +++ b/src/ConnectedMode/Binding/GlobalConfigGenerator.cs @@ -18,100 +18,91 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Linq; -using System.Collections.Generic; using System.Text; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.CSharpVB; using SonarQube.Client.Models; +using static SonarQube.Client.Models.SonarQubeSoftwareQualitySeverity; -namespace SonarLint.VisualStudio.ConnectedMode.Binding +namespace SonarLint.VisualStudio.ConnectedMode.Binding; + +public interface IGlobalConfigGenerator { - public interface IGlobalConfigGenerator - { - string Generate(IEnumerable rules); - } + string Generate(IEnumerable rules); +} - public class GlobalConfigGenerator : IGlobalConfigGenerator - { - private readonly IEnvironmentSettings environmentSettings; +public class GlobalConfigGenerator : IGlobalConfigGenerator +{ + private readonly IEnvironmentSettings environmentSettings; - private static readonly string inactiveRuleActionText = GetActionText(RuleAction.None); + private static readonly string inactiveRuleActionText = GetActionText(RuleAction.None); - public GlobalConfigGenerator() : this(new EnvironmentSettings()) { } + public GlobalConfigGenerator() : this(new EnvironmentSettings()) { } - public GlobalConfigGenerator(IEnvironmentSettings environmentSettings) - { - this.environmentSettings = environmentSettings; - } + public GlobalConfigGenerator(IEnvironmentSettings environmentSettings) + { + this.environmentSettings = environmentSettings; + } - public string Generate(IEnumerable rules) + public string Generate(IEnumerable rules) + { + if (rules == null) { - if (rules == null) - { - throw new ArgumentNullException(nameof(rules)); - } - - var sb = new StringBuilder(); - - sb.AppendLine("is_global=true"); - sb.AppendLine("global_level=1999999999"); - - var sortedRules = rules.OrderBy(r => r.Key); + throw new ArgumentNullException(nameof(rules)); + } - foreach (var rule in sortedRules) - { - var severityText = (rule.IsActive) ? GetActionText(GetVsSeverity(rule.Severity)) : inactiveRuleActionText; + var sb = new StringBuilder(); - sb.AppendLine($"dotnet_diagnostic.{rule.Key}.severity = {severityText}"); - } + sb.AppendLine("is_global=true"); + sb.AppendLine("global_level=1999999999"); - return sb.ToString(); - } + var sortedRules = rules.OrderBy(r => r.Key); - internal /* for testing */ RuleAction GetVsSeverity(SonarQubeIssueSeverity sqSeverity) + foreach (var rule in sortedRules) { - switch (sqSeverity) - { - case SonarQubeIssueSeverity.Info: - case SonarQubeIssueSeverity.Minor: - return RuleAction.Info; - - case SonarQubeIssueSeverity.Major: - case SonarQubeIssueSeverity.Critical: - return RuleAction.Warning; - - case SonarQubeIssueSeverity.Blocker: - return environmentSettings.TreatBlockerSeverityAsError() ? RuleAction.Error : RuleAction.Warning; - - default: - throw new NotSupportedException($"Unsupported SonarQube issue severity: {sqSeverity}"); - } - } - // See https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/configuration-options#severity-level - internal /* for testing */ static string GetActionText(RuleAction ruleAction) - { - switch (ruleAction) - { - case RuleAction.None: - return "none"; + var severityText = rule.IsActive ? GetActionText(GetVsSeverity(rule)) : inactiveRuleActionText; - case RuleAction.Info: - return "suggestion"; + sb.AppendLine($"dotnet_diagnostic.{rule.Key}.severity = {severityText}"); + } - case RuleAction.Warning: - return "warning"; + return sb.ToString(); + } - case RuleAction.Error: - return "error"; + internal /* for testing */ RuleAction GetVsSeverity(SonarQubeRule rule) => + GetVsSeverity(rule.SoftwareQualitySeverities?.Values) ?? GetVsSeverity(rule.Severity); - case RuleAction.Hidden: - return "silent"; + internal /* for testing */ RuleAction? GetVsSeverity(ICollection severities) => + severities is not { Count: > 0 } + ? null + : GetVsSeverity(severities.Max()); - default: - throw new NotSupportedException($"{ruleAction} is not a supported RuleAction."); - } - } - } + private RuleAction GetVsSeverity(SonarQubeSoftwareQualitySeverity severity) => + severity switch + { + Blocker or High => environmentSettings.TreatBlockerSeverityAsError() ? RuleAction.Error : RuleAction.Warning, + Medium => RuleAction.Warning, + Low or Info => RuleAction.Info, + _ => throw new NotSupportedException($"Unsupported SonarQube issue severity: {severity}") + }; + + internal /* for testing */ RuleAction GetVsSeverity(SonarQubeIssueSeverity sqSeverity) => + sqSeverity switch + { + SonarQubeIssueSeverity.Info or SonarQubeIssueSeverity.Minor => RuleAction.Info, + SonarQubeIssueSeverity.Major or SonarQubeIssueSeverity.Critical => RuleAction.Warning, + SonarQubeIssueSeverity.Blocker => environmentSettings.TreatBlockerSeverityAsError() ? RuleAction.Error : RuleAction.Warning, + _ => throw new NotSupportedException($"Unsupported SonarQube issue severity: {sqSeverity}") + }; + + // See https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/configuration-options#severity-level + internal /* for testing */ static string GetActionText(RuleAction ruleAction) => + ruleAction switch + { + RuleAction.None => "none", + RuleAction.Info => "suggestion", + RuleAction.Warning => "warning", + RuleAction.Error => "error", + RuleAction.Hidden => "silent", + _ => throw new NotSupportedException($"{ruleAction} is not a supported RuleAction.") + }; }