diff --git a/analyzers/src/SonarAnalyzer.Common/DiagnosticAnalyzer/ParametrizedDiagnosticAnalyzer.cs b/analyzers/src/SonarAnalyzer.Common/DiagnosticAnalyzer/ParametrizedDiagnosticAnalyzer.cs index c1ae6279f85..cb3d06010dd 100644 --- a/analyzers/src/SonarAnalyzer.Common/DiagnosticAnalyzer/ParametrizedDiagnosticAnalyzer.cs +++ b/analyzers/src/SonarAnalyzer.Common/DiagnosticAnalyzer/ParametrizedDiagnosticAnalyzer.cs @@ -32,7 +32,7 @@ protected sealed override void Initialize(SonarAnalysisContext context) context.RegisterCompilationStartAction( c => { - ParameterLoader.SetParameterValues(this, c.Options); + ParameterLoader.SetParameterValues(this, c.SonarLintFile()); parameterContext.ExecutePostponedActions(c); }); } diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/ParameterLoader.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/ParameterLoader.cs index b472cd3e53e..98772ed382c 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/ParameterLoader.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/ParameterLoader.cs @@ -19,10 +19,7 @@ */ using System.Globalization; -using System.IO; using System.Reflection; -using System.Xml; -using System.Xml.Linq; namespace SonarAnalyzer.Helpers { @@ -39,84 +36,30 @@ internal static class ParameterLoader * - diffing the contents of the configuration file * - associating the file with a unique identifier for the build project */ - internal static void SetParameterValues(ParametrizedDiagnosticAnalyzer parameteredAnalyzer, - AnalyzerOptions options) + internal static void SetParameterValues(ParametrizedDiagnosticAnalyzer parameteredAnalyzer, SonarLintXmlReader sonarLintXml) { - var sonarLintXml = options.SonarLintXml(); - if (sonarLintXml == null) - { - return; - } - - var parameters = ParseParameters(sonarLintXml); - if (parameters.IsEmpty) + if (!sonarLintXml.ParametrizedRules.Any()) { return; } var propertyParameterPairs = parameteredAnalyzer.GetType() .GetRuntimeProperties() - .Select(p => new { Property = p, Descriptor = p.GetCustomAttributes().SingleOrDefault() }) - .Where(p => p.Descriptor != null); + .Select(x => new { Property = x, Descriptor = x.GetCustomAttributes().SingleOrDefault() }) + .Where(x => x.Descriptor is not null); var ids = new HashSet(parameteredAnalyzer.SupportedDiagnostics.Select(diagnostic => diagnostic.Id)); foreach (var propertyParameterPair in propertyParameterPairs) { - var parameter = parameters - .FirstOrDefault(p => ids.Contains(p.RuleId)); - - var parameterValue = parameter?.ParameterValues - .FirstOrDefault(pv => pv.ParameterKey == propertyParameterPair.Descriptor.Key); - - if (TryConvertToParameterType(parameterValue?.ParameterValue, propertyParameterPair.Descriptor.Type, out var value)) + var parameter = sonarLintXml.ParametrizedRules.FirstOrDefault(x => ids.Contains(x.Key)); + var parameterValue = parameter?.Parameters.FirstOrDefault(x => x.Key == propertyParameterPair.Descriptor.Key); + if (TryConvertToParameterType(parameterValue?.Value, propertyParameterPair.Descriptor.Type, out var value)) { propertyParameterPair.Property.SetValue(parameteredAnalyzer, value); } } } - private static ImmutableList ParseParameters(AdditionalText sonarLintXml) - { - try - { - var xml = XDocument.Parse(sonarLintXml.GetText().ToString()); - return ParseParameters(xml); - } - catch (Exception ex) when (ex is IOException || ex is XmlException) - { - // cannot log exception - return ImmutableList.Create(); - } - } - - private static ImmutableList ParseParameters(XContainer xml) - { - var builder = ImmutableList.CreateBuilder(); - foreach (var rule in xml.Descendants("Rule").Where(e => e.Elements("Parameters").Any())) - { - var analyzerId = rule.Elements("Key").Single().Value; - - var parameterValues = rule - .Elements("Parameters").Single() - .Elements("Parameter") - .Select(e => new RuleParameterValue - { - ParameterKey = e.Elements("Key").Single().Value, - ParameterValue = e.Elements("Value").Single().Value - }); - - var pvs = new RuleParameterValues - { - RuleId = analyzerId - }; - pvs.ParameterValues.AddRange(parameterValues); - - builder.Add(pvs); - } - - return builder.ToImmutable(); - } - private static bool TryConvertToParameterType(string parameter, PropertyType type, out object result) { if (parameter == null) @@ -144,19 +87,5 @@ private static bool TryConvertToParameterType(string parameter, PropertyType typ return false; } } - - private sealed class RuleParameterValues - { - public string RuleId { get; set; } - - public List ParameterValues { get; } = new List(); - } - - private sealed class RuleParameterValue - { - public string ParameterKey { get; set; } - - public string ParameterValue { get; set; } - } } } diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs index 8c717d7a31d..83fb3a13080 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs @@ -34,30 +34,33 @@ public class SonarLintXmlReader private readonly string propertyLanguage; private bool? ignoreHeaderComments; - public bool IgnoreHeaderComments => ignoreHeaderComments ??= ReadBoolean(ReadProperty($"sonar.{propertyLanguage}.ignoreHeaderComments")); + public bool IgnoreHeaderComments => ignoreHeaderComments ??= ReadBoolean(ReadSettingsProperty($"sonar.{propertyLanguage}.ignoreHeaderComments")); private bool? analyzeGeneratedCode; - public bool AnalyzeGeneratedCode => analyzeGeneratedCode ??= ReadBoolean(ReadProperty($"sonar.{propertyLanguage}.analyzeGeneratedCode")); + public bool AnalyzeGeneratedCode => analyzeGeneratedCode ??= ReadBoolean(ReadSettingsProperty($"sonar.{propertyLanguage}.analyzeGeneratedCode")); private string[] exclusions; - public string[] Exclusions => exclusions ??= ReadCommaSeparatedArray(ReadProperty("sonar.exclusions")); + public string[] Exclusions => exclusions ??= ReadCommaSeparatedArray(ReadSettingsProperty("sonar.exclusions")); private string[] inclusions; - public string[] Inclusions => inclusions ??= ReadCommaSeparatedArray(ReadProperty("sonar.inclusions")); + public string[] Inclusions => inclusions ??= ReadCommaSeparatedArray(ReadSettingsProperty("sonar.inclusions")); private string[] globalExclusions; - public string[] GlobalExclusions => globalExclusions ??= ReadCommaSeparatedArray(ReadProperty("sonar.global.exclusions")); + public string[] GlobalExclusions => globalExclusions ??= ReadCommaSeparatedArray(ReadSettingsProperty("sonar.global.exclusions")); private string[] testExclusions; - public string[] TestExclusions => testExclusions ??= ReadCommaSeparatedArray(ReadProperty("sonar.test.exclusions")); + public string[] TestExclusions => testExclusions ??= ReadCommaSeparatedArray(ReadSettingsProperty("sonar.test.exclusions")); private string[] testInclusions; - public string[] TestInclusions => testInclusions ??= ReadCommaSeparatedArray(ReadProperty("sonar.test.inclusions")); + public string[] TestInclusions => testInclusions ??= ReadCommaSeparatedArray(ReadSettingsProperty("sonar.test.inclusions")); private string[] globalTestExclusions; - public string[] GlobalTestExclusions => globalTestExclusions ??= ReadCommaSeparatedArray(ReadProperty("sonar.global.test.exclusions")); + public string[] GlobalTestExclusions => globalTestExclusions ??= ReadCommaSeparatedArray(ReadSettingsProperty("sonar.global.test.exclusions")); - public SonarLintXmlReader(SourceText sonarLintXml, string language) + private List parametrizedRules; + public List ParametrizedRules => parametrizedRules ??= ReadRuleParameters(); + + public SonarLintXmlReader(SourceText sonarLintXml, string language = LanguageNames.CSharp) { this.sonarLintXml = sonarLintXml == null ? SonarLintXml.Empty : ParseContent(sonarLintXml); propertyLanguage = language == LanguageNames.CSharp ? "cs" : "vbnet"; @@ -80,7 +83,12 @@ private static SonarLintXml ParseContent(SourceText sonarLintXml) } } - private string ReadProperty(string property) => + private List ReadRuleParameters() => + sonarLintXml is { Rules: { } rules } + ? rules.Where(x => x.Parameters.Any()).ToList() + : new(); + + private string ReadSettingsProperty(string property) => sonarLintXml is { Settings: { } settings } ? settings.Where(x => x.Key.Equals(property)).Select(x => x.Value).FirstOrDefault() : string.Empty; diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs index 646ceb2fb34..bcf46ee97b6 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs @@ -20,6 +20,8 @@ using System.IO; using Microsoft.CodeAnalysis.Text; +using SonarAnalyzer.AnalysisContext; +using SonarAnalyzer.Common; using SonarAnalyzer.Rules.CSharp; namespace SonarAnalyzer.UnitTest.Helpers @@ -27,17 +29,19 @@ namespace SonarAnalyzer.UnitTest.Helpers [TestClass] public class ParameterLoaderTest { + public TestContext TestContext { get; set; } + [TestMethod] [DataRow("path//aSonarLint.xml")] // different name [DataRow("path//SonarLint.xmla")] // different extension public void SetParameterValues_WhenNoSonarLintIsGiven_DoesNotPopulateParameters(string filePath) { // Arrange - var options = AnalysisScaffolding.CreateOptions(filePath, SourceText.From(File.ReadAllText("ResourceTests\\SonarLint.xml"))); + var compilation = CreateCompilationWithOption(filePath, SourceText.From(File.ReadAllText("ResourceTests\\SonarLint.xml"))); var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, options); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); // Assert analyzer.Maximum.Should().Be(3); // Default value @@ -49,11 +53,11 @@ public void SetParameterValues_WhenNoSonarLintIsGiven_DoesNotPopulateParameters( public void SetParameterValues_WhenGivenValidSonarLintFilePath_PopulatesProperties(string filePath) { // Arrange - var options = AnalysisScaffolding.CreateOptions(filePath, SourceText.From(File.ReadAllText("ResourceTests\\SonarLint.xml"))); + var compilation = CreateCompilationWithOption(filePath, SourceText.From(File.ReadAllText("ResourceTests\\SonarLint.xml"))); var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, options); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); // Assert analyzer.Maximum.Should().Be(1); // Value from the xml file @@ -63,11 +67,11 @@ public void SetParameterValues_WhenGivenValidSonarLintFilePath_PopulatesProperti public void SetParameterValues_WhenGivenSonarLintFileHasIntParameterType_PopulatesProperties() { // Arrange - var options = AnalysisScaffolding.CreateOptions("ResourceTests\\SonarLint.xml"); + var compilation = CreateCompilationWithOption("ResourceTests\\SonarLint.xml"); var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, options); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); // Assert analyzer.Maximum.Should().Be(1); // Value from the xml file @@ -77,11 +81,11 @@ public void SetParameterValues_WhenGivenSonarLintFileHasIntParameterType_Populat public void SetParameterValues_WhenGivenSonarLintFileHasStringParameterType_OnlyOneParameter_PopulatesProperty() { // Arrange - var options = AnalysisScaffolding.CreateOptions("ResourceTests\\RuleWithStringParameter\\SonarLint.xml"); + var compilation = CreateCompilationWithOption("ResourceTests\\RuleWithStringParameter\\SonarLint.xml"); var analyzer = new EnumNameShouldFollowRegex(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, options); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); // Assert analyzer.FlagsEnumNamePattern.Should().Be("1"); // value from XML file @@ -91,11 +95,11 @@ public void SetParameterValues_WhenGivenSonarLintFileHasStringParameterType_Only public void SetParameterValues_WhenGivenSonarLintFileHasBooleanParameterType_OnlyOneParameter_PopulatesProperty() { // Arrange - var options = AnalysisScaffolding.CreateOptions("ResourceTests\\RuleWithBooleanParameter\\SonarLint.xml"); + var compilation = CreateCompilationWithOption("ResourceTests\\RuleWithBooleanParameter\\SonarLint.xml"); var analyzer = new CheckFileLicense(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, options); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); // Assert analyzer.IsRegularExpression.Should().BeTrue(); // value from XML file @@ -105,85 +109,54 @@ public void SetParameterValues_WhenGivenSonarLintFileHasBooleanParameterType_Onl public void SetParameterValues_WhenGivenValidSonarLintFileAndDoesNotContainAnalyzerParameters_DoesNotPopulateProperties() { // Arrange - var options = AnalysisScaffolding.CreateOptions("ResourceTests\\SonarLint.xml"); + var compilation = CreateCompilationWithOption("ResourceTests\\SonarLint.xml"); var analyzer = new LineLength(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, options); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); // Assert analyzer.Maximum.Should().Be(200); // Default value } - [TestMethod] - public void SetParameterValues_WithNonExistentPath_UsesInMemoryText() - { - // Arrange - const string fakeSonarLintXmlFilePath = "ThisPathDoesNotExist\\SonarLint.xml"; - const string sonarLintXmlContent = @" - - - - - S1067 - - - max - 1 - - - - - - -"; - - var options = AnalysisScaffolding.CreateOptions(fakeSonarLintXmlFilePath, SourceText.From(sonarLintXmlContent)); - var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. - - // Act - ParameterLoader.SetParameterValues(analyzer, options); - - // Assert - analyzer.Maximum.Should().Be(1); // In-memory value - } - [TestMethod] public void SetParameterValues_CalledTwiceAfterChangeInConfigFile_UpdatesProperties() { // Arrange - const string fakeSonarLintXmlFilePath = "ThisPathDoesNotExist\\SonarLint.xml"; - const string originalSonarLintXmlContent = @" - - - - - S1067 - - - max - 1 - - - - - - -"; - - var options = AnalysisScaffolding.CreateOptions(fakeSonarLintXmlFilePath, SourceText.From(originalSonarLintXmlContent)); + var maxValue = 1; + var ruleParameters = new List() + { + new SonarLintXmlRule() + { + Key = "S1067", + Parameters = new List() + { + new SonarLintXmlKeyValuePair() + { + Key = "max", + Value = maxValue.ToString() + } + } + } + }; + var sonarLintXml = AnalysisScaffolding.GenerateSonarLintXmlContent(rulesParameters: ruleParameters); + var filePath = TestHelper.WriteFile(TestContext, "SonarLint.xml", sonarLintXml); + var compilation = CreateCompilationWithOption(filePath); var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, options); - analyzer.Maximum.Should().Be(1); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + analyzer.Maximum.Should().Be(maxValue); // Modify the in-memory additional file - var modifiedSonarLintXmlContent = originalSonarLintXmlContent.Replace("1", "42"); - var modifiedOptions = AnalysisScaffolding.CreateOptions(fakeSonarLintXmlFilePath, SourceText.From(modifiedSonarLintXmlContent)); - - ParameterLoader.SetParameterValues(analyzer, modifiedOptions); - analyzer.Maximum.Should().Be(42); + maxValue = 42; + ruleParameters.First().Parameters.First().Value = maxValue.ToString(); + var modifiedSonarLintXml = AnalysisScaffolding.GenerateSonarLintXmlContent(rulesParameters: ruleParameters); + var modifiedFilePath = TestHelper.WriteFile(TestContext, "SonarLint.xml", modifiedSonarLintXml); + compilation = CreateCompilationWithOption(modifiedFilePath); + + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + analyzer.Maximum.Should().Be(maxValue); } [TestMethod] @@ -193,11 +166,11 @@ public void SetParameterValues_CalledTwiceAfterChangeInConfigFile_UpdatesPropert public void SetParameterValues_WithMalformedXml_DoesNotPopulateProperties(string sonarLintXmlContent) { // Arrange - var options = AnalysisScaffolding.CreateOptions("fakePath\\SonarLint.xml", SourceText.From(sonarLintXmlContent)); + var compilation = CreateCompilationWithOption("fakePath\\SonarLint.xml", SourceText.From(sonarLintXmlContent)); var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, options); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); // Assert analyzer.Maximum.Should().Be(3); // Default value @@ -207,11 +180,11 @@ public void SetParameterValues_WithMalformedXml_DoesNotPopulateProperties(string public void SetParameterValues_WithWrongPropertyType_StringInsteadOfInt_DoesNotPopulateProperties() { // Arrange - var options = AnalysisScaffolding.CreateOptions("ResourceTests\\StringInsteadOfInt\\SonarLint.xml"); + var compilation = CreateCompilationWithOption("ResourceTests\\StringInsteadOfInt\\SonarLint.xml"); var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, options); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); // Assert analyzer.Maximum.Should().Be(3); // Default value @@ -221,14 +194,24 @@ public void SetParameterValues_WithWrongPropertyType_StringInsteadOfInt_DoesNotP public void SetParameterValues_WithWrongPropertyType_StringInsteadOfBoolean_DoesNotPopulateProperties() { // Arrange - var options = AnalysisScaffolding.CreateOptions("ResourceTests\\StringInsteadOfBoolean\\SonarLint.xml"); + var compilation = CreateCompilationWithOption("ResourceTests\\StringInsteadOfBoolean\\SonarLint.xml"); var analyzer = new CheckFileLicense(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, options); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); // Assert analyzer.IsRegularExpression.Should().BeFalse(); // Default value } + + private static SonarCompilationReportingContext CreateCompilationWithOption(string filePath, SourceText text = null) + { + var options = text is null + ? AnalysisScaffolding.CreateOptions(filePath) + : AnalysisScaffolding.CreateOptions(filePath, text); + var compilation = SolutionBuilder.Create().AddProject(AnalyzerLanguage.CSharp).GetCompilation(); + var compilationContext = new CompilationAnalysisContext(compilation, options, _ => { }, _ => true, default); + return new(AnalysisScaffolding.CreateSonarAnalysisContext(), compilationContext); + } } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs index b8502beb93f..737148185f8 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs @@ -41,6 +41,13 @@ public void SonarLintXmlReader_WhenAllValuesAreSet_ExpectedValues(string languag AssertArrayContent(sut.TestInclusions, nameof(sut.TestInclusions)); AssertArrayContent(sut.GlobalTestExclusions, nameof(sut.GlobalTestExclusions)); + sut.ParametrizedRules.Should().HaveCount(8); + var rule = sut.ParametrizedRules.First(x => x.Key.Equals("S2342")); + rule.Parameters[0].Key.Should().Be("format"); + rule.Parameters[0].Value.Should().Be("^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$"); + rule.Parameters[1].Key.Should().Be("flagsAttributeFormat"); + rule.Parameters[1].Value.Should().Be("^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?s$"); + static void AssertArrayContent(string[] array, string folder) { array.Should().HaveCount(2); @@ -61,6 +68,7 @@ public void SonarLintXmlReader_PartiallyMissingProperties_ExpectedAndDefaultValu sut.TestExclusions.Should().NotBeNull().And.HaveCount(0); sut.TestInclusions.Should().NotBeNull().And.HaveCount(0); sut.GlobalTestExclusions.Should().NotBeNull().And.HaveCount(0); + sut.ParametrizedRules.Should().NotBeNull().And.HaveCount(0); } [DataTestMethod] @@ -92,6 +100,7 @@ private static void CheckSonarLintXmlReaderDefaultValues(SonarLintXmlReader sut) sut.TestExclusions.Should().NotBeNull().And.HaveCount(0); sut.TestInclusions.Should().NotBeNull().And.HaveCount(0); sut.GlobalTestExclusions.Should().NotBeNull().And.HaveCount(0); + sut.ParametrizedRules.Should().NotBeNull().And.HaveCount(0); } private static void AssertArrayContent(string[] array, string folder) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/AnalysisScaffolding.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/AnalysisScaffolding.cs index 5f518397a40..aa476e1131e 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/AnalysisScaffolding.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/AnalysisScaffolding.cs @@ -19,6 +19,7 @@ */ using System.IO; +using System.Runtime.CompilerServices; using System.Xml.Linq; using Microsoft.CodeAnalysis.Text; using Moq; @@ -90,8 +91,9 @@ public static string CreateSonarProjectConfigWithFilesToAnalyze(TestContext cont string[] globalExclusions = null, string[] testExclusions = null, string[] testInclusions = null, - string[] globalTestExclusions = null) => - TestHelper.WriteFile(context, "SonarLint.xml", GenerateSonarLintXmlContent(language, analyzeGeneratedCode, exclusions, inclusions, globalExclusions, testExclusions, testInclusions, globalTestExclusions)); + string[] globalTestExclusions = null, + List rulesParameters = null) => + TestHelper.WriteFile(context, "SonarLint.xml", GenerateSonarLintXmlContent(language, analyzeGeneratedCode, exclusions, inclusions, globalExclusions, testExclusions, testInclusions, globalTestExclusions, rulesParameters)); public static string GenerateSonarLintXmlContent( string language = LanguageNames.CSharp, @@ -101,7 +103,8 @@ public static string CreateSonarProjectConfigWithFilesToAnalyze(TestContext cont string[] globalExclusions = null, string[] testExclusions = null, string[] testInclusions = null, - string[] globalTestExclusions = null) => + string[] globalTestExclusions = null, + List rulesParameters = null) => new XDocument( new XDeclaration("1.0", "utf-8", "yes"), new XElement("AnalysisInput", @@ -112,10 +115,32 @@ public static string CreateSonarProjectConfigWithFilesToAnalyze(TestContext cont CreateSetting("sonar.global.exclusions", ConcatenateStringArray(globalExclusions)), CreateSetting("sonar.test.exclusions", ConcatenateStringArray(testExclusions)), CreateSetting("sonar.test.inclusions", ConcatenateStringArray(testInclusions)), - CreateSetting("sonar.global.test.exclusions", ConcatenateStringArray(globalTestExclusions))))).ToString(); + CreateSetting("sonar.global.test.exclusions", ConcatenateStringArray(globalTestExclusions))), + new XElement("Rules", CreateRules(rulesParameters)))).ToString(); + + private static IEnumerable CreateRules(List ruleParameters) + { + foreach (var rule in ruleParameters ?? new()) + { + yield return CreateRule(rule); + } + } + + private static XElement CreateRule(SonarLintXmlRule rule) + { + List elements = new(); + foreach (var param in rule.Parameters) + { + elements.Add(CreateKeyValuePair("Parameter", param.Key, param.Value)); + } + return new("Rule", new XElement("Key", rule.Key), new XElement("Parameters", elements)); + } private static XElement CreateSetting(string key, string value) => - new("Setting", new XElement("Key", key), new XElement("Value", value)); + CreateKeyValuePair("Setting", key, value); + + private static XElement CreateKeyValuePair(string containerName, string key, string value) => + new(containerName, new XElement("Key", key), new XElement("Value", value)); private static string ConcatenateStringArray(string[] array) => string.Join(",", array ?? Array.Empty());