From 5ed52635d236d8aacbad433df2927479747b7087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=8Caba=20=C5=A0agi?= <75226367+csaba-sagi-sonarsource@users.noreply.github.com> Date: Thu, 2 Mar 2023 12:28:27 +0100 Subject: [PATCH 01/14] Add support for wildcard to regex mapping (#6835) * Implement pattern widlcardpattern matching logic * Address review comments * Add parameter validation --- .../Helpers/WildcardPatternMatcher.cs | 134 ++++++++++++++++++ .../Helpers/WildcardPatternMatcherTest.cs | 129 +++++++++++++++++ 2 files changed, 263 insertions(+) create mode 100644 analyzers/src/SonarAnalyzer.Common/Helpers/WildcardPatternMatcher.cs create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/Helpers/WildcardPatternMatcherTest.cs diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/WildcardPatternMatcher.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/WildcardPatternMatcher.cs new file mode 100644 index 00000000000..b72a702d8e9 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/WildcardPatternMatcher.cs @@ -0,0 +1,134 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact 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.Collections.Concurrent; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; + +namespace SonarAnalyzer.Helpers; + +internal static class WildcardPatternMatcher +{ + public static bool IsMatch(string pattern, string input) => + !(string.IsNullOrWhiteSpace(pattern) || string.IsNullOrWhiteSpace(input)) + && WildcardPattern.Create(pattern).Match(input); + + /// + /// Copied from https://github.com/SonarSource/sonar-plugin-api/blob/a9bd7ff48f0f77811ed909070030678c443c975a/sonar-plugin-api/src/main/java/org/sonar/api/utils/WildcardPattern.java. + /// + private sealed class WildcardPattern + { + private const string SpecialChars = "()[]^$.{}+|"; + private static readonly ConcurrentDictionary Cache = new(); + private readonly Regex pattern; + + private WildcardPattern(string pattern, string directorySeparator) => + this.pattern = new Regex(ToRegexp(pattern, directorySeparator), RegexOptions.Compiled, RegexConstants.DefaultTimeout); + + public bool Match(string value) + { + value = value.TrimStart('/'); + value = value.TrimEnd('/'); + try + { + return pattern.IsMatch(value); + } + catch (RegexMatchTimeoutException) + { + return false; + } + } + + public static WildcardPattern Create(string pattern) => + Create(pattern, Path.DirectorySeparatorChar.ToString()); + + private static WildcardPattern Create(string pattern, string directorySeparator) => + Cache.GetOrAdd(pattern + directorySeparator, _ => new WildcardPattern(pattern, directorySeparator)); + + private static string ToRegexp(string wildcardPattern, string directorySeparator) + { + var escapedDirectorySeparator = '\\' + directorySeparator; + var sb = new StringBuilder(wildcardPattern.Length); + + sb.Append('^'); + + var i = wildcardPattern.StartsWith("/") || wildcardPattern.StartsWith("\\") ? 1 : 0; + while (i < wildcardPattern.Length) + { + var ch = wildcardPattern[i]; + + if (SpecialChars.IndexOf(ch) != -1) + { + // Escape regexp-specific characters + sb.Append('\\').Append(ch); + } + else if (ch == '*') + { + if (i + 1 < wildcardPattern.Length && wildcardPattern[i + 1] == '*') + { + // Double asterisk + // Zero or more directories + if (i + 2 < wildcardPattern.Length && IsSlash(wildcardPattern[i + 2])) + { + sb.Append("(?:.*").Append(escapedDirectorySeparator).Append("|)"); + i += 2; + } + else + { + sb.Append(".*"); + i += 1; + } + } + else + { + // Single asterisk + // Zero or more characters excluding directory separator + sb.Append("[^").Append(escapedDirectorySeparator).Append("]*?"); + } + } + else if (ch == '?') + { + // Any single character excluding directory separator + sb.Append("[^").Append(escapedDirectorySeparator).Append("]"); + } + else if (IsSlash(ch)) + { + // Directory separator + sb.Append(escapedDirectorySeparator); + } + else + { + // Single character + sb.Append(ch); + } + + i++; + } + + sb.Append('$'); + + return sb.ToString(); + } + + private static bool IsSlash(char ch) => + ch == '/' || ch == '\\'; + } +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/WildcardPatternMatcherTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/WildcardPatternMatcherTest.cs new file mode 100644 index 00000000000..f8b02ce4aa8 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/WildcardPatternMatcherTest.cs @@ -0,0 +1,129 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact 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.IO; + +namespace SonarAnalyzer.UnitTest.Helpers +{ + [TestClass] + public class WildcardPatternMatcherTest + { + /// + /// Based on https://github.com/SonarSource/sonar-plugin-api/blob/master/plugin-api/src/test/java/org/sonar/api/utils/WildcardPatternTest.java. + /// + [DataTestMethod] + + [DataRow("Foo", "Foo", true)] + [DataRow("foo", "FOO", false)] + [DataRow("Foo", "Foot", false)] + [DataRow("Foo", "Bar", false)] + + [DataRow("org/T?st.java", "org/Test.java", true)] + [DataRow("org/T?st.java", "org/Tost.java", true)] + [DataRow("org/T?st.java", "org/Teeest.java", false)] + + [DataRow("org/*.java", "org/Foo.java", true)] + [DataRow("org/*.java", "org/Bar.java", true)] + + [DataRow("org/**", "org/Foo.java", true)] + [DataRow("org/**", "org/foo/bar.jsp", true)] + + [DataRow("org/**/Test.java", "org/Test.java", true)] + [DataRow("org/**/Test.java", "org/foo/Test.java", true)] + [DataRow("org/**/Test.java", "org/foo/bar/Test.java", true)] + + [DataRow("org/**/*.java", "org/Foo.java", true)] + [DataRow("org/**/*.java", "org/foo/Bar.java", true)] + [DataRow("org/**/*.java", "org/foo/bar/Baz.java", true)] + + [DataRow("o?/**/*.java", "org/test.java", false)] + [DataRow("o?/**/*.java", "o/test.java", false)] + [DataRow("o?/**/*.java", "og/test.java", true)] + [DataRow("o?/**/*.java", "og/foo/bar/test.java", true)] + [DataRow("o?/**/*.java", "og/foo/bar/test.jav", false)] + + [DataRow("org/sonar/**", "org/sonar/commons/Foo", true)] + [DataRow("org/sonar/**", "org/sonar/Foo.java", true)] + + [DataRow("xxx/org/sonar/**", "org/sonar/Foo", false)] + + [DataRow("org/sonar/**/**", "org/sonar/commons/Foo", true)] + [DataRow("org/sonar/**/**", "org/sonar/commons/sub/Foo.java", true)] + + [DataRow("org/sonar/**/Foo", "org/sonar/commons/sub/Foo", true)] + [DataRow("org/sonar/**/Foo", "org/sonar/Foo", true)] + + [DataRow("*/foo/*", "org/foo/Bar", true)] + [DataRow("*/foo/*", "foo/Bar", false)] + [DataRow("*/foo/*", "foo", false)] + [DataRow("*/foo/*", "org/foo/bar/Hello", false)] + + [DataRow("hell?", "hell", false)] + [DataRow("hell?", "hello", true)] + [DataRow("hell?", "helloworld", false)] + + [DataRow("**/Reader", "java/io/Reader", true)] + [DataRow("**/Reader", "org/sonar/channel/CodeReader", false)] + + [DataRow("**", "java/io/Reader", true)] + + [DataRow("**/app/**", "com/app/Utils", true)] + [DataRow("**/app/**", "com/application/MyService", false)] + + [DataRow("**/*$*", "foo/bar", false)] + [DataRow("**/*$*", "foo/bar$baz", true)] + [DataRow("a+", "aa", false)] + [DataRow("a+", "a+", true)] + [DataRow("[ab]", "a", false)] + [DataRow("[ab]", "[ab]", true)] + + [DataRow("\\n", "\n", false)] + [DataRow("foo\\bar", "foo/bar", true)] + + [DataRow("/foo", "foo", true)] + [DataRow("\\foo", "foo", true)] + + [DataRow("foo\\bar", "foo\\bar", true)] + [DataRow("foo/bar", "foo\\bar", true)] + [DataRow("foo\\bar/baz", "foo\\bar\\baz", true)] + + public void IsMatch_MatchesPatternsAsExpected(string pattern, string input, bool expectedResult) + { + // The test cases are copied from the plugin-api and the directory separators need replacing as Roslyn will not give us the paths with '/'. + input = input.Replace("/", Path.DirectorySeparatorChar.ToString()); + + WildcardPatternMatcher.IsMatch(pattern, input).Should().Be(expectedResult); + } + + [DataTestMethod] + [DataRow("")] + [DataRow(" ")] + [DataRow("/")] + [DataRow("\\")] + public void IsMatch_InvalidPattern_ReturnsFalse(string pattern) => + WildcardPatternMatcher.IsMatch(pattern, "foo").Should().BeFalse(); + + [DataTestMethod] + [DataRow(null, "foo")] + [DataRow("foo", null)] + public void IsMatch_InputParametersArenull_DoesNotThrow(string pattern, string input) => + WildcardPatternMatcher.IsMatch(pattern, input).Should().BeFalse(); + } +} From a88884b06861bf7932e22cd3feb81d917cce55ea Mon Sep 17 00:00:00 2001 From: Cristian Ambrosini <114916336+cristian-ambrosini-sonarsource@users.noreply.github.com> Date: Fri, 3 Mar 2023 17:09:12 +0100 Subject: [PATCH 02/14] Create data class for SonarLint.xml and reading mechanism for reading the file (#6832) --- .../SonarAnalysisContextBase.cs | 23 + .../Helpers/SonarLintXml.cs | 59 ++ .../Helpers/SonarLintXmlReader.cs | 93 ++ .../SonarAnalysisContextBaseTest.cs | 93 +- .../Helpers/SonarLintXmlReaderTest.cs | 106 +++ .../Helpers/SonarLintXmlTest.cs | 82 ++ .../All_properties_cs/SonarLint.xml | 889 ++++++++++++++++++ .../SonarLint.xml | 81 ++ .../All_properties_vbnet/SonarLint.xml | 889 ++++++++++++++++++ .../Incorrect_value_type/SonarLint.xml | 21 + .../SonarLintXml/Invalid_Xml/SonarLint.xml | 13 + .../Missing_properties/SonarLint.xml | 13 + .../SonarLint.xml | 25 + 13 files changed, 2386 insertions(+), 1 deletion(-) create mode 100644 analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXml.cs create mode 100644 analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlTest.cs create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_cs/SonarLint.xml create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_small_template/SonarLint.xml create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_vbnet/SonarLint.xml create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/Incorrect_value_type/SonarLint.xml create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/Invalid_Xml/SonarLint.xml create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/Missing_properties/SonarLint.xml create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/Partially_missing_properties/SonarLint.xml diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs index e18e7365ae7..4b0b506499d 100644 --- a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs @@ -28,11 +28,16 @@ public class SonarAnalysisContextBase { protected static readonly ConditionalWeakTable> UnchangedFilesCache = new(); protected static readonly SourceTextValueProvider ProjectConfigProvider = new(x => new ProjectConfigReader(x)); + protected static readonly SourceTextValueProvider SonarLintXmlProviderCS = new(x => new SonarLintXmlReader(x, LanguageNames.CSharp)); + protected static readonly SourceTextValueProvider SonarLintXmlProviderVB = new(x => new SonarLintXmlReader(x, LanguageNames.VisualBasic)); private static readonly Lazy> ShouldAnalyzeGeneratedCS = new(() => CreateAnalyzeGeneratedProvider(LanguageNames.CSharp)); private static readonly Lazy> ShouldAnalyzeGeneratedVB = new(() => CreateAnalyzeGeneratedProvider(LanguageNames.VisualBasic)); protected SonarAnalysisContextBase() { } + protected static SourceTextValueProvider SonarLintXmlReader(string language) => + language == LanguageNames.CSharp ? SonarLintXmlProviderCS : SonarLintXmlProviderVB; + protected static SourceTextValueProvider ShouldAnalyzeGeneratedProvider(string language) => language == LanguageNames.CSharp ? ShouldAnalyzeGeneratedCS.Value : ShouldAnalyzeGeneratedVB.Value; @@ -80,6 +85,24 @@ public ProjectConfigReader ProjectConfiguration() } } + /// + /// Reads the properties from the SonarLint.xml file and caches the result for the scope of this analysis. + /// + public SonarLintXmlReader SonarLintFile() + { + if (Options.SonarLintXml() is { } sonarLintXml) + { + return sonarLintXml.GetText() is { } sourceText + && AnalysisContext.TryGetValue(sourceText, SonarLintXmlReader(Compilation.Language), out var sonarLintXmlReader) + ? sonarLintXmlReader + : throw new InvalidOperationException($"File '{Path.GetFileName(sonarLintXml.Path)}' has been added as an AdditionalFile but could not be read and parsed."); + } + else + { + return Helpers.SonarLintXmlReader.Empty; + } + } + public bool IsTestProject() { var projectType = ProjectConfiguration().ProjectType; diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXml.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXml.cs new file mode 100644 index 00000000000..d699d960638 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXml.cs @@ -0,0 +1,59 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact 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.Xml.Serialization; + +namespace SonarAnalyzer.Helpers; + +/// +/// DTO to represent the SonarLint.xml for our analyzers. +/// +/// +/// This class should not be used in this codebase. To get SonarLint.xml properties, use . +/// +[XmlRoot(ElementName = "AnalysisInput")] +public class SonarLintXml +{ + public static readonly SonarLintXml Empty = new(); + + [XmlArray("Settings")] + [XmlArrayItem("Setting")] + public List Settings { get; set; } + + [XmlArray("Rules")] + [XmlArrayItem("Rule")] + public List Rules { get; set; } +} + +public class SonarLintXmlRule +{ + [XmlElement("Key")] + public string Key { get; set; } + + [XmlArray("Parameters")] + [XmlArrayItem("Parameter")] + public List Parameters { get; set; } +} + +public class SonarLintXmlKeyValuePair +{ + public string Key { get; set; } + public string Value { get; set; } +} diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs new file mode 100644 index 00000000000..8c717d7a31d --- /dev/null +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs @@ -0,0 +1,93 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact 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.IO; +using System.Text; +using System.Xml; +using System.Xml.Serialization; +using Microsoft.CodeAnalysis.Text; + +namespace SonarAnalyzer.Helpers; + +public class SonarLintXmlReader +{ + public static readonly SonarLintXmlReader Empty = new(null, LanguageNames.CSharp); + + private readonly SonarLintXml sonarLintXml; + private readonly string propertyLanguage; + + private bool? ignoreHeaderComments; + public bool IgnoreHeaderComments => ignoreHeaderComments ??= ReadBoolean(ReadProperty($"sonar.{propertyLanguage}.ignoreHeaderComments")); + + private bool? analyzeGeneratedCode; + public bool AnalyzeGeneratedCode => analyzeGeneratedCode ??= ReadBoolean(ReadProperty($"sonar.{propertyLanguage}.analyzeGeneratedCode")); + + private string[] exclusions; + public string[] Exclusions => exclusions ??= ReadCommaSeparatedArray(ReadProperty("sonar.exclusions")); + + private string[] inclusions; + public string[] Inclusions => inclusions ??= ReadCommaSeparatedArray(ReadProperty("sonar.inclusions")); + + private string[] globalExclusions; + public string[] GlobalExclusions => globalExclusions ??= ReadCommaSeparatedArray(ReadProperty("sonar.global.exclusions")); + + private string[] testExclusions; + public string[] TestExclusions => testExclusions ??= ReadCommaSeparatedArray(ReadProperty("sonar.test.exclusions")); + + private string[] testInclusions; + public string[] TestInclusions => testInclusions ??= ReadCommaSeparatedArray(ReadProperty("sonar.test.inclusions")); + + private string[] globalTestExclusions; + public string[] GlobalTestExclusions => globalTestExclusions ??= ReadCommaSeparatedArray(ReadProperty("sonar.global.test.exclusions")); + + public SonarLintXmlReader(SourceText sonarLintXml, string language) + { + this.sonarLintXml = sonarLintXml == null ? SonarLintXml.Empty : ParseContent(sonarLintXml); + propertyLanguage = language == LanguageNames.CSharp ? "cs" : "vbnet"; + } + + private static SonarLintXml ParseContent(SourceText sonarLintXml) + { + try + { + var serializer = new XmlSerializer(typeof(SonarLintXml)); + var byteArray = Encoding.UTF8.GetBytes(sonarLintXml.ToString()); + var stream = new MemoryStream(byteArray); + using var sr = new StreamReader(stream, Encoding.UTF8, false); + using var reader = XmlReader.Create(sr); + return (SonarLintXml)serializer.Deserialize(reader); + } + catch + { + return SonarLintXml.Empty; + } + } + + private string ReadProperty(string property) => + sonarLintXml is { Settings: { } settings } + ? settings.Where(x => x.Key.Equals(property)).Select(x => x.Value).FirstOrDefault() + : string.Empty; + + private static string[] ReadCommaSeparatedArray(string str) => + string.IsNullOrEmpty(str) ? Array.Empty() : str.Split(','); + + private static bool ReadBoolean(string str, bool defaultValue = false) => + bool.TryParse(str, out var propertyValue) ? propertyValue : defaultValue; +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs index d402e7b82a4..54c4b1dc9fe 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs @@ -19,6 +19,7 @@ */ using SonarAnalyzer.AnalysisContext; +using SonarAnalyzer.Common; namespace SonarAnalyzer.UnitTest.AnalysisContext; @@ -131,7 +132,7 @@ public void ProjectConfiguration_WhenFileChanges_RebuildsCache() secondConfig.Should().NotBeSameAs(firstConfig); } - [TestMethod] + [DataTestMethod] [DataRow(null)] [DataRow("/foo/bar/does-not-exit")] [DataRow("/foo/bar/x.xml")] @@ -170,6 +171,96 @@ public void ProjectConfiguration_WhenInvalidXml_ThrowException() .WithMessage("File 'SonarProjectConfig.xml' has been added as an AdditionalFile but could not be read and parsed."); } + [DataTestMethod] + [DataRow("cs")] + [DataRow("vbnet")] + public void SonarLintFile_LoadsExpectedValues(string language) + { + var analyzerLanguage = language == "cs" ? AnalyzerLanguage.CSharp : AnalyzerLanguage.VisualBasic; + var (compilation, _) = CreateDummyCompilation(analyzerLanguage, "ExtraEmptyFile"); + var options = AnalysisScaffolding.CreateOptions($"ResourceTests\\SonarLintXml\\All_properties_{language}\\SonarLint.xml"); + var sut = CreateSut(compilation, options).SonarLintFile(); + + sut.IgnoreHeaderComments.Should().BeTrue(); + sut.AnalyzeGeneratedCode.Should().BeFalse(); + AssertArrayContent(sut.Exclusions, nameof(sut.Exclusions)); + AssertArrayContent(sut.Inclusions, nameof(sut.Inclusions)); + AssertArrayContent(sut.GlobalExclusions, nameof(sut.GlobalExclusions)); + AssertArrayContent(sut.TestExclusions, nameof(sut.TestExclusions)); + AssertArrayContent(sut.TestInclusions, nameof(sut.TestInclusions)); + AssertArrayContent(sut.GlobalTestExclusions, nameof(sut.GlobalTestExclusions)); + + static void AssertArrayContent(string[] array, string folder) + { + array.Should().HaveCount(2); + array[0].Should().BeEquivalentTo($"Fake/{folder}/**/*"); + array[1].Should().BeEquivalentTo($"Fake/{folder}/Second*/**/*"); + } + } + + [TestMethod] + public void SonarLintFile_UsesCachedValue() + { + var options = AnalysisScaffolding.CreateOptions("ResourceTests\\SonarLintXml\\All_properties_cs\\SonarLint.xml"); + var firstSut = CreateSut(options); + var secondSut = CreateSut(options); + var firstFile = firstSut.SonarLintFile(); + var secondFile = secondSut.SonarLintFile(); + + secondFile.Should().BeSameAs(firstFile); + } + + [TestMethod] + public void SonarLintFile_WhenFileChanges_RebuildsCache() + { + var firstOptions = AnalysisScaffolding.CreateOptions("ResourceTests\\SonarLintXml\\All_properties_cs\\SonarLint.xml"); + var secondOptions = AnalysisScaffolding.CreateOptions("ResourceTests\\SonarLintXml\\All_properties_vbnet\\SonarLint.xml"); + var firstFile = CreateSut(firstOptions).SonarLintFile(); + var secondFile = CreateSut(secondOptions).SonarLintFile(); + + secondFile.Should().NotBeSameAs(firstFile); + } + + [DataTestMethod] + [DataRow(null)] + [DataRow("\\foo\\bar\\does-not-exit")] + [DataRow("\\foo\\bar\\x.xml")] + public void SonarLintFile_WhenAdditionalFileNotPresent_ReturnsDefaultValues(string folder) + { + var sut = CreateSut(AnalysisScaffolding.CreateOptions(folder)).SonarLintFile(); + CheckSonarLintXmlDefaultValues(sut); + } + + [TestMethod] + public void SonarLintFile_WhenInvalidXml_ReturnsDefaultValues() + { + var sut = CreateSut(AnalysisScaffolding.CreateOptions("ResourceTests\\SonarLintXml\\Invalid_Xml\\SonarLint.xml")).SonarLintFile(); + CheckSonarLintXmlDefaultValues(sut); + } + + [TestMethod] + public void SonarLintFile_WhenFileIsMissing_ThrowException() + { + var sut = CreateSut(AnalysisScaffolding.CreateOptions("ThisPathDoesNotExist\\SonarLint.xml")); + + sut.Invoking(x => x.SonarLintFile()) + .Should() + .Throw() + .WithMessage("File 'SonarLint.xml' has been added as an AdditionalFile but could not be read and parsed."); + } + + private static void CheckSonarLintXmlDefaultValues(SonarLintXmlReader sut) + { + sut.AnalyzeGeneratedCode.Should().BeFalse(); + sut.IgnoreHeaderComments.Should().BeFalse(); + sut.Exclusions.Should().NotBeNull().And.HaveCount(0); + sut.Inclusions.Should().NotBeNull().And.HaveCount(0); + sut.GlobalExclusions.Should().NotBeNull().And.HaveCount(0); + sut.TestExclusions.Should().NotBeNull().And.HaveCount(0); + sut.TestInclusions.Should().NotBeNull().And.HaveCount(0); + sut.GlobalTestExclusions.Should().NotBeNull().And.HaveCount(0); + } + private SonarCompilationReportingContext CreateSut(ProjectType projectType, bool isScannerRun) => CreateSut(AnalysisScaffolding.CreateOptions(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, projectType, isScannerRun))); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs new file mode 100644 index 00000000000..b8502beb93f --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs @@ -0,0 +1,106 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact 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.IO; +using Microsoft.CodeAnalysis.Text; + +namespace SonarAnalyzer.UnitTest.Helpers; + +[TestClass] +public class SonarLintXmlReaderTest +{ + [DataTestMethod] + [DataRow(LanguageNames.CSharp, "cs")] + [DataRow(LanguageNames.VisualBasic, "vbnet")] + public void SonarLintXmlReader_WhenAllValuesAreSet_ExpectedValues(string language, string propertyLanguage) + { + var sut = CreateSonarLintXmlReader($"ResourceTests\\SonarLintXml\\All_Properties_{propertyLanguage}\\SonarLint.xml", language); + sut.IgnoreHeaderComments.Should().BeTrue(); + sut.AnalyzeGeneratedCode.Should().BeFalse(); + AssertArrayContent(sut.Exclusions, nameof(sut.Exclusions)); + AssertArrayContent(sut.Inclusions, nameof(sut.Inclusions)); + AssertArrayContent(sut.GlobalExclusions, nameof(sut.GlobalExclusions)); + AssertArrayContent(sut.TestExclusions, nameof(sut.TestExclusions)); + AssertArrayContent(sut.TestInclusions, nameof(sut.TestInclusions)); + AssertArrayContent(sut.GlobalTestExclusions, nameof(sut.GlobalTestExclusions)); + + static void AssertArrayContent(string[] array, string folder) + { + array.Should().HaveCount(2); + array[0].Should().BeEquivalentTo($"Fake/{folder}/**/*"); + array[1].Should().BeEquivalentTo($"Fake/{folder}/Second*/**/*"); + } + } + + [TestMethod] + public void SonarLintXmlReader_PartiallyMissingProperties_ExpectedAndDefaultValues() + { + var sut = CreateSonarLintXmlReader("ResourceTests\\SonarLintXml\\Partially_missing_properties\\SonarLint.xml"); + sut.IgnoreHeaderComments.Should().BeFalse(); + sut.AnalyzeGeneratedCode.Should().BeTrue(); + AssertArrayContent(sut.Exclusions, nameof(sut.Exclusions)); + AssertArrayContent(sut.Inclusions, nameof(sut.Inclusions)); + sut.GlobalExclusions.Should().NotBeNull().And.HaveCount(0); + sut.TestExclusions.Should().NotBeNull().And.HaveCount(0); + sut.TestInclusions.Should().NotBeNull().And.HaveCount(0); + sut.GlobalTestExclusions.Should().NotBeNull().And.HaveCount(0); + } + + [DataTestMethod] + [DataRow("")] + [DataRow("this is not an xml")] + [DataRow(@"")] + public void SonarLintXmlReader_WithMalformedXml_DefaultBehaviour(string sonarLintXmlContent) => + CheckSonarLintXmlReaderDefaultValues(new SonarLintXmlReader(SourceText.From(sonarLintXmlContent), LanguageNames.CSharp)); + + [TestMethod] + public void SonarLintXmlReader_MissingProperties_DefaultBehaviour() => + CheckSonarLintXmlReaderDefaultValues(CreateSonarLintXmlReader("ResourceTests\\SonarLintXml\\Missing_properties\\SonarLint.xml")); + + [TestMethod] + public void SonarLintXmlReader_WithIncorrectValueType_DefaultBehaviour() => + CheckSonarLintXmlReaderDefaultValues(CreateSonarLintXmlReader("ResourceTests\\SonarLintXml\\Incorrect_value_type\\SonarLint.xml")); + + [TestMethod] + public void SonarLintXmlReader_CheckEmpty_DefaultBehaviour() => + CheckSonarLintXmlReaderDefaultValues(SonarLintXmlReader.Empty); + + private static void CheckSonarLintXmlReaderDefaultValues(SonarLintXmlReader sut) + { + sut.AnalyzeGeneratedCode.Should().BeFalse(); + sut.IgnoreHeaderComments.Should().BeFalse(); + sut.Exclusions.Should().NotBeNull().And.HaveCount(0); + sut.Inclusions.Should().NotBeNull().And.HaveCount(0); + sut.GlobalExclusions.Should().NotBeNull().And.HaveCount(0); + sut.TestExclusions.Should().NotBeNull().And.HaveCount(0); + sut.TestInclusions.Should().NotBeNull().And.HaveCount(0); + sut.GlobalTestExclusions.Should().NotBeNull().And.HaveCount(0); + } + + private static void AssertArrayContent(string[] array, string folder) + { + array.Should().HaveCount(2); + array[0].Should().BeEquivalentTo($"Fake/{folder}/**/*"); + array[1].Should().BeEquivalentTo($"Fake/{folder}/Second*/**/*"); + } + + private static SonarLintXmlReader CreateSonarLintXmlReader(string relativePath, string language = LanguageNames.CSharp) => + new(SourceText.From(File.ReadAllText(relativePath)), language); +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlTest.cs new file mode 100644 index 00000000000..b27c8f4b38d --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlTest.cs @@ -0,0 +1,82 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact 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.IO; +using System.Xml.Serialization; + +namespace SonarAnalyzer.UnitTest.Helpers; + +[TestClass] +public class SonarLintXmlTest +{ + [TestMethod] + public void SonarLintXml_DeserializeFile_ExpectedValues() + { + var deserializer = new XmlSerializer(typeof(SonarLintXml)); + using TextReader textReader = new StreamReader("ResourceTests\\SonarLintXml\\All_properties_small_template\\SonarLint.xml"); + var sonarLintXml = (SonarLintXml)deserializer.Deserialize(textReader); + + AssertSettings(sonarLintXml.Settings); + AssertRules(sonarLintXml.Rules); + } + + private static void AssertSettings(List settings) + { + settings.Should().HaveCount(10); + + AssertKeyValuePair(settings[0], "sonar.cs.ignoreHeaderComments", "true"); + AssertKeyValuePair(settings[1], "sonar.cs.analyzeGeneratedCode", "false"); + AssertKeyValuePair(settings[2], "sonar.cs.file.suffixes", ".cs"); + AssertKeyValuePair(settings[3], "sonar.cs.roslyn.ignoreIssues", "false"); + AssertKeyValuePair(settings[4], "sonar.exclusions", "Fake/Exclusions/**/*,Fake/Exclusions/Second*/**/*"); + AssertKeyValuePair(settings[5], "sonar.inclusions", "Fake/Inclusions/**/*,Fake/Inclusions/Second*/**/*"); + AssertKeyValuePair(settings[6], "sonar.global.exclusions", "Fake/GlobalExclusions/**/*,Fake/GlobalExclusions/Second*/**/*"); + AssertKeyValuePair(settings[7], "sonar.test.exclusions", "Fake/TestExclusions/**/*,Fake/TestExclusions/Second*/**/*"); + AssertKeyValuePair(settings[8], "sonar.test.inclusions", "Fake/TestInclusions/**/*,Fake/TestInclusions/Second*/**/*"); + AssertKeyValuePair(settings[9], "sonar.global.test.exclusions", "Fake/GlobalTestExclusions/**/*,Fake/GlobalTestExclusions/Second*/**/*"); + } + + private static void AssertRules(List rules) + { + rules.Should().HaveCount(4); + rules.Where(x => x.Parameters.Any()).Should().HaveCount(2); + + rules[0].Key.Should().BeEquivalentTo("S0001"); + rules[0].Parameters.Should().BeEmpty(); + rules[1].Key.Should().BeEquivalentTo("S0002"); + rules[1].Parameters.Should().BeEmpty(); + + rules[2].Key.Should().BeEquivalentTo("S0003"); + rules[2].Parameters.Should().HaveCount(2); + AssertKeyValuePair(rules[2].Parameters[0], "format", "^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$"); + AssertKeyValuePair(rules[2].Parameters[1], "flagsAttributeFormat", "^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?s$"); + + rules[3].Key.Should().BeEquivalentTo("S0004"); + rules[3].Parameters.Should().HaveCount(2); + AssertKeyValuePair(rules[3].Parameters[0], "threshold", "15"); + AssertKeyValuePair(rules[3].Parameters[1], "propertyThreshold", "3"); + } + + private static void AssertKeyValuePair(SonarLintXmlKeyValuePair pair, string expectedKey, string expectedValue) + { + pair.Key.Should().BeEquivalentTo(expectedKey); + pair.Value.Should().BeEquivalentTo(expectedValue); + } +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_cs/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_cs/SonarLint.xml new file mode 100644 index 00000000000..0dc284bc00d --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_cs/SonarLint.xml @@ -0,0 +1,889 @@ + + + + + sonar.cs.ignoreHeaderComments + true + + + sonar.cs.analyzeGeneratedCode + false + + + sonar.cs.file.suffixes + .cs + + + sonar.cs.roslyn.ignoreIssues + false + + + sonar.exclusions + Fake/Exclusions/**/*,Fake/Exclusions/Second*/**/* + + + sonar.inclusions + Fake/Inclusions/**/*,Fake/Inclusions/Second*/**/* + + + sonar.global.exclusions + Fake/GlobalExclusions/**/*,Fake/GlobalExclusions/Second*/**/* + + + sonar.test.exclusions + Fake/TestExclusions/**/*,Fake/TestExclusions/Second*/**/* + + + sonar.test.inclusions + Fake/TestInclusions/**/*,Fake/TestInclusions/Second*/**/* + + + sonar.global.test.exclusions + Fake/GlobalTestExclusions/**/*,Fake/GlobalTestExclusions/Second*/**/* + + + + + S2225 + + + S2346 + + + S2589 + + + S3433 + + + S1135 + + + S2223 + + + S2344 + + + S2345 + + + S4524 + + + S1134 + + + S2222 + + + S2342 + + + format + ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$ + + + flagsAttributeFormat + ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?s$ + + + + + S2115 + + + S2583 + + + S3447 + + + S2234 + + + S2479 + + + S3444 + + + S3445 + + + S1144 + + + S1264 + + + S2114 + + + S3442 + + + S3443 + + + S3329 + + + S3440 + + + S3449 + + + S3655 + + + S3776 + + + threshold + 15 + + + propertyThreshold + 3 + + + + + S3897 + + + S2201 + + + S2688 + + + S4502 + + + S1110 + + + S1117 + + + S1118 + + + S2328 + + + S2681 + + + S2326 + + + S3415 + + + S4507 + + + S1116 + + + S1125 + + + S1479 + + + maximum + 30 + + + + + S2699 + + + S4635 + + + S1123 + + + S2696 + + + S1121 + + + S2692 + + + S1006 + + + S1481 + + + S2219 + + + S3237 + + + S3427 + + + S3236 + + + S3358 + + + S3598 + + + S2386 + + + S3597 + + + S4200 + + + S5773 + + + S1172 + + + S4201 + + + S5659 + + + S3249 + + + S4456 + + + S4457 + + + S3005 + + + S3246 + + + S3247 + + + S5547 + + + S3244 + + + S4211 + + + S5542 + + + S1066 + + + S4210 + + + S1185 + + + S1186 + + + S2275 + + + S2368 + + + S3241 + + + S3457 + + + S3458 + + + S4423 + + + S1155 + + + S2245 + + + S3453 + + + S3456 + + + S4426 + + + S2123 + + + S2365 + + + S2486 + + + S3451 + + + S3330 + + + S4428 + + + S5753 + + + S3217 + + + S3218 + + + S3459 + + + S927 + + + S3450 + + + S5766 + + + S1048 + + + S1168 + + + S2259 + + + S3466 + + + S2257 + + + S3343 + + + S3346 + + + S3464 + + + S2376 + + + S3220 + + + S4433 + + + S1163 + + + S2252 + + + S4790 + + + S818 + + + S2251 + + + S2372 + + + S2743 + + + S4792 + + + S1656 + + + S907 + + + S2995 + + + S3600 + + + S2996 + + + S3963 + + + S2755 + + + S2757 + + + S3604 + + + S2997 + + + S3603 + + + S3966 + + + S1751 + + + S1871 + + + S1643 + + + S1764 + + + S2737 + + + S2971 + + + S2612 + + + S2857 + + + S3875 + + + S3871 + + + S1210 + + + S1450 + + + S2306 + + + S3877 + + + S3998 + + + S1104 + + + S1215 + + + S1699 + + + S3887 + + + S3400 + + + S3884 + + + S3885 + + + S2551 + + + S3881 + + + S2436 + + + maxMethod + 3 + + + max + 2 + + + + + S3889 + + + S1313 + + + S2437 + + + S3972 + + + S2761 + + + S3610 + + + S3973 + + + S3971 + + + S3984 + + + S4830 + + + S3981 + + + S1206 + + + S3626 + + + S3869 + + + S1940 + + + S1944 + + + S1939 + + + S1905 + + + S5034 + + + S4061 + + + S5042 + + + S1854 + + + S4070 + + + S1862 + + + S3925 + + + S3926 + + + S3927 + + + S3928 + + + S2953 + + + S3923 + + + S125 + + + S1607 + + + S1848 + + + S2930 + + + S2933 + + + S3903 + + + S3904 + + + S2934 + + + S6422 + + + S110 + + + max + 5 + + + + + S2068 + + + credentialWords + password, passwd, pwd, passphrase + + + + + S5332 + + + S112 + + + S6424 + + + S2187 + + + S3397 + + + S4487 + + + S2184 + + + S6420 + + + S2183 + + + S5693 + + + fileUploadSizeLimit + 8000000 + + + + + S107 + + + max + 7 + + + + + S108 + + + S3169 + + + S4019 + + + S3168 + + + S4015 + + + S4136 + + + S2077 + + + S2190 + + + S1199 + + + S3376 + + + S3011 + + + S3256 + + + S1075 + + + S4581 + + + S4586 + + + S3010 + + + S3251 + + + S4220 + + + S4583 + + + S2178 + + + S3264 + + + S3267 + + + S5443 + + + S101 + + + S3265 + + + S5445 + + + S3262 + + + S6419 + + + S2053 + + + S3263 + + + S2292 + + + S3260 + + + S3261 + + + S2290 + + + S2291 + + + S4144 + + + S6444 + + + S3172 + + + S4143 + + + S4159 + + + S4260 + + + S4036 + + + S4158 + + + S4277 + + + S4035 + + + S4275 + + + S2092 + + + S3060 + + + S5122 + + + + + diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_small_template/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_small_template/SonarLint.xml new file mode 100644 index 00000000000..70ca0115566 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_small_template/SonarLint.xml @@ -0,0 +1,81 @@ + + + + + sonar.cs.ignoreHeaderComments + true + + + sonar.cs.analyzeGeneratedCode + false + + + sonar.cs.file.suffixes + .cs + + + sonar.cs.roslyn.ignoreIssues + false + + + sonar.exclusions + Fake/Exclusions/**/*,Fake/Exclusions/Second*/**/* + + + sonar.inclusions + Fake/Inclusions/**/*,Fake/Inclusions/Second*/**/* + + + sonar.global.exclusions + Fake/GlobalExclusions/**/*,Fake/GlobalExclusions/Second*/**/* + + + sonar.test.exclusions + Fake/TestExclusions/**/*,Fake/TestExclusions/Second*/**/* + + + sonar.test.inclusions + Fake/TestInclusions/**/*,Fake/TestInclusions/Second*/**/* + + + sonar.global.test.exclusions + Fake/GlobalTestExclusions/**/*,Fake/GlobalTestExclusions/Second*/**/* + + + + + S0001 + + + S0002 + + + S0003 + + + format + ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$ + + + flagsAttributeFormat + ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?s$ + + + + + S0004 + + + threshold + 15 + + + propertyThreshold + 3 + + + + + + + diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_vbnet/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_vbnet/SonarLint.xml new file mode 100644 index 00000000000..f848061efac --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_vbnet/SonarLint.xml @@ -0,0 +1,889 @@ + + + + + sonar.vbnet.ignoreHeaderComments + true + + + sonar.vbnet.analyzeGeneratedCode + false + + + sonar.vbnet.file.suffixes + .vb + + + sonar.vbnet.roslyn.ignoreIssues + false + + + sonar.exclusions + Fake/Exclusions/**/*,Fake/Exclusions/Second*/**/* + + + sonar.inclusions + Fake/Inclusions/**/*,Fake/Inclusions/Second*/**/* + + + sonar.global.exclusions + Fake/GlobalExclusions/**/*,Fake/GlobalExclusions/Second*/**/* + + + sonar.test.exclusions + Fake/TestExclusions/**/*,Fake/TestExclusions/Second*/**/* + + + sonar.test.inclusions + Fake/TestInclusions/**/*,Fake/TestInclusions/Second*/**/* + + + sonar.global.test.exclusions + Fake/GlobalTestExclusions/**/*,Fake/GlobalTestExclusions/Second*/**/* + + + + + S2225 + + + S2346 + + + S2589 + + + S3433 + + + S1135 + + + S2223 + + + S2344 + + + S2345 + + + S4524 + + + S1134 + + + S2222 + + + S2342 + + + format + ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$ + + + flagsAttributeFormat + ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?s$ + + + + + S2115 + + + S2583 + + + S3447 + + + S2234 + + + S2479 + + + S3444 + + + S3445 + + + S1144 + + + S1264 + + + S2114 + + + S3442 + + + S3443 + + + S3329 + + + S3440 + + + S3449 + + + S3655 + + + S3776 + + + threshold + 15 + + + propertyThreshold + 3 + + + + + S3897 + + + S2201 + + + S2688 + + + S4502 + + + S1110 + + + S1117 + + + S1118 + + + S2328 + + + S2681 + + + S2326 + + + S3415 + + + S4507 + + + S1116 + + + S1125 + + + S1479 + + + maximum + 30 + + + + + S2699 + + + S4635 + + + S1123 + + + S2696 + + + S1121 + + + S2692 + + + S1006 + + + S1481 + + + S2219 + + + S3237 + + + S3427 + + + S3236 + + + S3358 + + + S3598 + + + S2386 + + + S3597 + + + S4200 + + + S5773 + + + S1172 + + + S4201 + + + S5659 + + + S3249 + + + S4456 + + + S4457 + + + S3005 + + + S3246 + + + S3247 + + + S5547 + + + S3244 + + + S4211 + + + S5542 + + + S1066 + + + S4210 + + + S1185 + + + S1186 + + + S2275 + + + S2368 + + + S3241 + + + S3457 + + + S3458 + + + S4423 + + + S1155 + + + S2245 + + + S3453 + + + S3456 + + + S4426 + + + S2123 + + + S2365 + + + S2486 + + + S3451 + + + S3330 + + + S4428 + + + S5753 + + + S3217 + + + S3218 + + + S3459 + + + S927 + + + S3450 + + + S5766 + + + S1048 + + + S1168 + + + S2259 + + + S3466 + + + S2257 + + + S3343 + + + S3346 + + + S3464 + + + S2376 + + + S3220 + + + S4433 + + + S1163 + + + S2252 + + + S4790 + + + S818 + + + S2251 + + + S2372 + + + S2743 + + + S4792 + + + S1656 + + + S907 + + + S2995 + + + S3600 + + + S2996 + + + S3963 + + + S2755 + + + S2757 + + + S3604 + + + S2997 + + + S3603 + + + S3966 + + + S1751 + + + S1871 + + + S1643 + + + S1764 + + + S2737 + + + S2971 + + + S2612 + + + S2857 + + + S3875 + + + S3871 + + + S1210 + + + S1450 + + + S2306 + + + S3877 + + + S3998 + + + S1104 + + + S1215 + + + S1699 + + + S3887 + + + S3400 + + + S3884 + + + S3885 + + + S2551 + + + S3881 + + + S2436 + + + maxMethod + 3 + + + max + 2 + + + + + S3889 + + + S1313 + + + S2437 + + + S3972 + + + S2761 + + + S3610 + + + S3973 + + + S3971 + + + S3984 + + + S4830 + + + S3981 + + + S1206 + + + S3626 + + + S3869 + + + S1940 + + + S1944 + + + S1939 + + + S1905 + + + S5034 + + + S4061 + + + S5042 + + + S1854 + + + S4070 + + + S1862 + + + S3925 + + + S3926 + + + S3927 + + + S3928 + + + S2953 + + + S3923 + + + S125 + + + S1607 + + + S1848 + + + S2930 + + + S2933 + + + S3903 + + + S3904 + + + S2934 + + + S6422 + + + S110 + + + max + 5 + + + + + S2068 + + + credentialWords + password, passwd, pwd, passphrase + + + + + S5332 + + + S112 + + + S6424 + + + S2187 + + + S3397 + + + S4487 + + + S2184 + + + S6420 + + + S2183 + + + S5693 + + + fileUploadSizeLimit + 8000000 + + + + + S107 + + + max + 7 + + + + + S108 + + + S3169 + + + S4019 + + + S3168 + + + S4015 + + + S4136 + + + S2077 + + + S2190 + + + S1199 + + + S3376 + + + S3011 + + + S3256 + + + S1075 + + + S4581 + + + S4586 + + + S3010 + + + S3251 + + + S4220 + + + S4583 + + + S2178 + + + S3264 + + + S3267 + + + S5443 + + + S101 + + + S3265 + + + S5445 + + + S3262 + + + S6419 + + + S2053 + + + S3263 + + + S2292 + + + S3260 + + + S3261 + + + S2290 + + + S2291 + + + S4144 + + + S6444 + + + S3172 + + + S4143 + + + S4159 + + + S4260 + + + S4036 + + + S4158 + + + S4277 + + + S4035 + + + S4275 + + + S2092 + + + S3060 + + + S5122 + + + + + diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/Incorrect_value_type/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/Incorrect_value_type/SonarLint.xml new file mode 100644 index 00000000000..ff59ba614a5 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/Incorrect_value_type/SonarLint.xml @@ -0,0 +1,21 @@ + + + + + sonar.cs.ignoreHeaderComments + abc + + + sonar.cs.analyzeGeneratedCode + null + + + sonar.cs.roslyn.ignoreIssues + + + + + + + + diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/Invalid_Xml/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/Invalid_Xml/SonarLint.xml new file mode 100644 index 00000000000..9a259cbeb32 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/Invalid_Xml/SonarLint.xml @@ -0,0 +1,13 @@ + + + Setting> + sonar.nothing + nothing + + + + + Files> + + diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/Missing_properties/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/Missing_properties/SonarLint.xml new file mode 100644 index 00000000000..8d8f8135bfc --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/Missing_properties/SonarLint.xml @@ -0,0 +1,13 @@ + + + + + sonar.nothing + nothing + + + + + + + diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/Partially_missing_properties/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/Partially_missing_properties/SonarLint.xml new file mode 100644 index 00000000000..ed671af08b6 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/Partially_missing_properties/SonarLint.xml @@ -0,0 +1,25 @@ + + + + + sonar.nothing + nothing + + + sonar.cs.analyzeGeneratedCode + true + + + sonar.exclusions + Fake/Exclusions/**/*,Fake/Exclusions/Second*/**/* + + + sonar.inclusions + Fake/Inclusions/**/*,Fake/Inclusions/Second*/**/* + + + + + + + From 27838e7176a61a95db68e6d70bbcd8f78619d7c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=8Caba=20=C5=A0agi?= <75226367+csaba-sagi-sonarsource@users.noreply.github.com> Date: Mon, 6 Mar 2023 10:54:33 +0100 Subject: [PATCH 03/14] Use new way of retrieving the IgnoreHeaderComments parameter (#6858) --- analyzers/src/SonarAnalyzer.Common/Helpers/PropertiesHelper.cs | 3 --- .../Rules/Utilities/UtilityAnalyzerBase.cs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/PropertiesHelper.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/PropertiesHelper.cs index 55844a6ba58..ec7286f082b 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/PropertiesHelper.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/PropertiesHelper.cs @@ -40,9 +40,6 @@ public static XElement[] ParseXmlSettings(SourceText sourceText) public static bool ReadAnalyzeGeneratedCodeProperty(IEnumerable settings, string language) => ReadBooleanProperty(settings, language, "analyzeGeneratedCode"); - public static bool ReadIgnoreHeaderCommentsProperty(IEnumerable settings, string language) => - ReadBooleanProperty(settings, language, "ignoreHeaderComments"); - private static bool ReadBooleanProperty(IEnumerable settings, string language, string propertySuffix, bool defaultValue = false) { var propertyLanguage = language == LanguageNames.CSharp ? "cs" : "vbnet"; diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index 9ccc48ce48f..d8c35d9468d 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -62,7 +62,7 @@ protected void ReadParameters(SonarCompilationStartAnalysisContext context) if (settings.Any() && !string.IsNullOrEmpty(outPath)) { var language = context.Compilation.Language; - IgnoreHeaderComments = PropertiesHelper.ReadIgnoreHeaderCommentsProperty(settings, language); + IgnoreHeaderComments = context.SonarLintFile().IgnoreHeaderComments; AnalyzeGeneratedCode = PropertiesHelper.ReadAnalyzeGeneratedCodeProperty(settings, language); OutPath = Path.Combine(outPath, language == LanguageNames.CSharp ? "output-cs" : "output-vbnet"); IsAnalyzerEnabled = true; From 37ba063ae380e5a4512f3081added3e138cd347b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=8Caba=20=C5=A0agi?= <75226367+csaba-sagi-sonarsource@users.noreply.github.com> Date: Tue, 7 Mar 2023 13:45:11 +0100 Subject: [PATCH 04/14] Use new way of reading should analyze generated parameter (#6865) --- .../SonarAnalysisContextBase.cs | 15 +---- .../Extensions/AnalyzerOptionsExtensions.cs | 4 -- .../Helpers/PropertiesHelper.cs | 57 ---------------- .../Rules/Utilities/UtilityAnalyzerBase.cs | 11 ++- .../SonarAnalysisContextBaseTest.cs | 2 + .../Helpers/PropertiesHelperTest.cs | 67 ------------------- .../ResourceTests/NoSettings/SonarLint.xml | 16 ----- .../ResourceTests/NotBoolean/SonarLint.xml | 30 --------- 8 files changed, 8 insertions(+), 194 deletions(-) delete mode 100644 analyzers/src/SonarAnalyzer.Common/Helpers/PropertiesHelper.cs delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/Helpers/PropertiesHelperTest.cs delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/NoSettings/SonarLint.xml delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/NotBoolean/SonarLint.xml diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs index 4b0b506499d..d1d0a4ab8e2 100644 --- a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs @@ -30,19 +30,11 @@ public class SonarAnalysisContextBase protected static readonly SourceTextValueProvider ProjectConfigProvider = new(x => new ProjectConfigReader(x)); protected static readonly SourceTextValueProvider SonarLintXmlProviderCS = new(x => new SonarLintXmlReader(x, LanguageNames.CSharp)); protected static readonly SourceTextValueProvider SonarLintXmlProviderVB = new(x => new SonarLintXmlReader(x, LanguageNames.VisualBasic)); - private static readonly Lazy> ShouldAnalyzeGeneratedCS = new(() => CreateAnalyzeGeneratedProvider(LanguageNames.CSharp)); - private static readonly Lazy> ShouldAnalyzeGeneratedVB = new(() => CreateAnalyzeGeneratedProvider(LanguageNames.VisualBasic)); protected SonarAnalysisContextBase() { } protected static SourceTextValueProvider SonarLintXmlReader(string language) => language == LanguageNames.CSharp ? SonarLintXmlProviderCS : SonarLintXmlProviderVB; - - protected static SourceTextValueProvider ShouldAnalyzeGeneratedProvider(string language) => - language == LanguageNames.CSharp ? ShouldAnalyzeGeneratedCS.Value : ShouldAnalyzeGeneratedVB.Value; - - private static SourceTextValueProvider CreateAnalyzeGeneratedProvider(string language) => - new(x => PropertiesHelper.ReadAnalyzeGeneratedCodeProperty(PropertiesHelper.ParseXmlSettings(x), language)); } public abstract class SonarAnalysisContextBase : SonarAnalysisContextBase @@ -63,7 +55,7 @@ protected SonarAnalysisContextBase(SonarAnalysisContext analysisContext, TContex /// Tree to decide on. Can be null for Symbol-based and Compilation-based scenarios. And we want to analyze those too. /// When set, generated trees are analyzed only when language-specific 'analyzeGeneratedCode' configuration property is also set. public bool ShouldAnalyzeTree(SyntaxTree tree, GeneratedCodeRecognizer generatedCodeRecognizer) => - (generatedCodeRecognizer is null || ShouldAnalyzeGenerated() || !tree.IsGenerated(generatedCodeRecognizer, Compilation)) + (generatedCodeRecognizer is null || SonarLintFile().AnalyzeGeneratedCode || !tree.IsGenerated(generatedCodeRecognizer, Compilation)) && (tree is null || !IsUnchanged(tree)); /// @@ -132,9 +124,4 @@ public bool HasMatchingScope(DiagnosticDescriptor descriptor) private ImmutableHashSet CreateUnchangedFilesHashSet() => ImmutableHashSet.Create(StringComparer.OrdinalIgnoreCase, ProjectConfiguration().AnalysisConfig?.UnchangedFiles() ?? Array.Empty()); - - private bool ShouldAnalyzeGenerated() => - Options.SonarLintXml() is { } sonarLintXml - && AnalysisContext.TryGetValue(sonarLintXml.GetText(), ShouldAnalyzeGeneratedProvider(Compilation.Language), out var shouldAnalyzeGenerated) - && shouldAnalyzeGenerated; } diff --git a/analyzers/src/SonarAnalyzer.Common/Extensions/AnalyzerOptionsExtensions.cs b/analyzers/src/SonarAnalyzer.Common/Extensions/AnalyzerOptionsExtensions.cs index 6cea60b71a5..d3ed1f510bd 100644 --- a/analyzers/src/SonarAnalyzer.Common/Extensions/AnalyzerOptionsExtensions.cs +++ b/analyzers/src/SonarAnalyzer.Common/Extensions/AnalyzerOptionsExtensions.cs @@ -19,7 +19,6 @@ */ using System.IO; -using System.Xml.Linq; namespace SonarAnalyzer.Extensions; @@ -34,9 +33,6 @@ public static class AnalyzerOptionsExtensions public static AdditionalText ProjectOutFolderPath(this AnalyzerOptions options) => options.AdditionalFile("ProjectOutFolderPath.txt"); - public static XElement[] ParseSonarLintXmlSettings(this AnalyzerOptions options) => - options.SonarLintXml() is { } sonarLintXml ? PropertiesHelper.ParseXmlSettings(sonarLintXml.GetText()) : Array.Empty(); - private static AdditionalText AdditionalFile(this AnalyzerOptions options, string fileName) => options.AdditionalFiles.FirstOrDefault(x => x.Path is not null && Path.GetFileName(x.Path).Equals(fileName, StringComparison.OrdinalIgnoreCase)); } diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/PropertiesHelper.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/PropertiesHelper.cs deleted file mode 100644 index ec7286f082b..00000000000 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/PropertiesHelper.cs +++ /dev/null @@ -1,57 +0,0 @@ -/* - * SonarAnalyzer for .NET - * Copyright (C) 2015-2023 SonarSource SA - * mailto: contact 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.Xml.Linq; -using Microsoft.CodeAnalysis.Text; - -namespace SonarAnalyzer.Helpers -{ - internal static class PropertiesHelper - { - public static XElement[] ParseXmlSettings(SourceText sourceText) - { - try - { - return XDocument.Parse(sourceText.ToString()).Descendants("Setting").ToArray(); - } - catch - { - return Array.Empty(); // Can not log the exception, so ignore it - } - } - - public static bool ReadAnalyzeGeneratedCodeProperty(IEnumerable settings, string language) => - ReadBooleanProperty(settings, language, "analyzeGeneratedCode"); - - private static bool ReadBooleanProperty(IEnumerable settings, string language, string propertySuffix, bool defaultValue = false) - { - var propertyLanguage = language == LanguageNames.CSharp ? "cs" : "vbnet"; - var propertyName = $"sonar.{propertyLanguage}.{propertySuffix}"; - return settings.Any() - && GetPropertyStringValue(propertyName) is { } propertyStringValue - && bool.TryParse(propertyStringValue, out var propertyValue) - ? propertyValue - : defaultValue; - - string GetPropertyStringValue(string propName) => - settings.FirstOrDefault(s => s.Element("Key")?.Value == propName)?.Element("Value").Value; - } - } -} diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index d8c35d9468d..6df9b93a53e 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -52,19 +52,18 @@ public abstract class UtilityAnalyzerBase : SonarDiagnosticAnalyzer protected void ReadParameters(SonarCompilationStartAnalysisContext context) { - var settings = context.Options.ParseSonarLintXmlSettings(); var outPath = context.ProjectConfiguration().OutPath; // For backward compatibility with S4MSB <= 5.0 if (outPath == null && context.Options.ProjectOutFolderPath() is { } projectOutFolderAdditionalFile) { outPath = projectOutFolderAdditionalFile.GetText().ToString().TrimEnd(); } - if (settings.Any() && !string.IsNullOrEmpty(outPath)) + if (context.Options.SonarLintXml() != null && !string.IsNullOrEmpty(outPath)) { - var language = context.Compilation.Language; - IgnoreHeaderComments = context.SonarLintFile().IgnoreHeaderComments; - AnalyzeGeneratedCode = PropertiesHelper.ReadAnalyzeGeneratedCodeProperty(settings, language); - OutPath = Path.Combine(outPath, language == LanguageNames.CSharp ? "output-cs" : "output-vbnet"); + var sonarLintXml = context.SonarLintFile(); + IgnoreHeaderComments = sonarLintXml.IgnoreHeaderComments; + AnalyzeGeneratedCode = sonarLintXml.AnalyzeGeneratedCode; + OutPath = Path.Combine(outPath, context.Compilation.Language == LanguageNames.CSharp ? "output-cs" : "output-vbnet"); IsAnalyzerEnabled = true; IsTestProject = context.IsTestProject(); } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs index 54c4b1dc9fe..e0c98dd49aa 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs @@ -225,6 +225,8 @@ public void SonarLintFile_WhenFileChanges_RebuildsCache() [DataRow(null)] [DataRow("\\foo\\bar\\does-not-exit")] [DataRow("\\foo\\bar\\x.xml")] + [DataRow("path//aSonarLint.xml")] // different name + [DataRow("path//SonarLint.xmla")] // different extension public void SonarLintFile_WhenAdditionalFileNotPresent_ReturnsDefaultValues(string folder) { var sut = CreateSut(AnalysisScaffolding.CreateOptions(folder)).SonarLintFile(); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/PropertiesHelperTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/PropertiesHelperTest.cs deleted file mode 100644 index acb23ca9d43..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/PropertiesHelperTest.cs +++ /dev/null @@ -1,67 +0,0 @@ -/* - * SonarAnalyzer for .NET - * Copyright (C) 2015-2023 SonarSource SA - * mailto: contact 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.IO; -using Microsoft.CodeAnalysis.Text; -using SonarAnalyzer.Extensions; - -namespace SonarAnalyzer.UnitTest.Helpers -{ - [TestClass] - public class PropertiesHelperTest - { - [TestMethod] - [DataRow("a/SonarLint.xml")] // unix path - [DataRow("a\\SonarLint.xml")] - public void ShouldAnalyzeGeneratedCode_WithTrueSetting_ReturnsTrue(string filePath) => - GetSetting(SourceText.From(File.ReadAllText("ResourceTests\\AnalyzeGeneratedTrue\\SonarLint.xml")), filePath).Should().BeTrue(); - - [TestMethod] - public void ShouldAnalyzeGeneratedCode_WithFalseSetting_ReturnsFalse() => - GetSetting(SourceText.From(File.ReadAllText("ResourceTests\\AnalyzeGeneratedFalse\\SonarLint.xml"))).Should().BeFalse(); - - [TestMethod] - public void ShouldAnalyzeGeneratedCode_WithNoSetting_ReturnsFalse() => - GetSetting(SourceText.From(File.ReadAllText("ResourceTests\\NoSettings\\SonarLint.xml"))).Should().BeFalse(); - - [TestMethod] - [DataRow("")] - [DataRow("this is not an xml")] - [DataRow(@"")] - public void ShouldAnalyzeGeneratedCode_WithMalformedXml_ReturnsFalse(string sonarLintXmlContent) => - GetSetting(SourceText.From(sonarLintXmlContent)).Should().BeFalse(); - - [TestMethod] - public void ShouldAnalyzeGeneratedCode_WithNotBooleanValue_ReturnsFalse() => - GetSetting(SourceText.From(File.ReadAllText("ResourceTests\\NotBoolean\\SonarLint.xml"))).Should().BeFalse(); - - [TestMethod] - [DataRow("path//aSonarLint.xml")] // different name - [DataRow("path//SonarLint.xmla")] // different extension - public void ShouldAnalyzeGeneratedCode_NonSonarLintXmlPath_ReturnsFalse(string filePath) => - GetSetting(SourceText.From(File.ReadAllText("ResourceTests\\AnalyzeGeneratedTrue\\SonarLint.xml")), filePath).Should().BeFalse(); - - private static bool GetSetting(SourceText text, string path = "fakePath\\SonarLint.xml") - { - var options = AnalysisScaffolding.CreateOptions(path, text); - return PropertiesHelper.ReadAnalyzeGeneratedCodeProperty(options.ParseSonarLintXmlSettings(), LanguageNames.CSharp); - } - } -} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/NoSettings/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/NoSettings/SonarLint.xml deleted file mode 100644 index 7a00c312796..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/NoSettings/SonarLint.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - S1067 - - - max - 1 - - - - - - - diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/NotBoolean/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/NotBoolean/SonarLint.xml deleted file mode 100644 index fd29e9c1f0a..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/NotBoolean/SonarLint.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - sonar.cs.ignoreHeaderComments - not-boolean-value - - - sonar.cs.analyzeGeneratedCode - not-boolean-value - - - sonar.cs.file.suffixes - .cs - - - - - S1067 - - - max - 1 - - - - - - - From 3bfd511e5b404afa22e897772cd31ee6df2b5153 Mon Sep 17 00:00:00 2001 From: Cristian Ambrosini <114916336+cristian-ambrosini-sonarsource@users.noreply.github.com> Date: Thu, 9 Mar 2023 16:31:26 +0100 Subject: [PATCH 05/14] Apply exclusion inclusion logic for all rules (except utility) (#6860) --- .../SonarAnalysisContextBase.cs | 28 +++- ...alysisContextBaseTest.ShouldAnalyzeTree.cs | 156 +++++++++++++----- .../TestFramework/AnalysisScaffolding.cs | 40 +++++ 3 files changed, 183 insertions(+), 41 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs index d1d0a4ab8e2..70fd96b25ee 100644 --- a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using System.Collections.Concurrent; using System.IO; using System.Runtime.CompilerServices; using static SonarAnalyzer.Helpers.DiagnosticDescriptorFactory; @@ -26,6 +27,7 @@ namespace SonarAnalyzer.AnalysisContext; public class SonarAnalysisContextBase { + protected static readonly ConditionalWeakTable> FileInclusionCache = new(); protected static readonly ConditionalWeakTable> UnchangedFilesCache = new(); protected static readonly SourceTextValueProvider ProjectConfigProvider = new(x => new ProjectConfigReader(x)); protected static readonly SourceTextValueProvider SonarLintXmlProviderCS = new(x => new SonarLintXmlReader(x, LanguageNames.CSharp)); @@ -55,8 +57,9 @@ protected SonarAnalysisContextBase(SonarAnalysisContext analysisContext, TContex /// Tree to decide on. Can be null for Symbol-based and Compilation-based scenarios. And we want to analyze those too. /// When set, generated trees are analyzed only when language-specific 'analyzeGeneratedCode' configuration property is also set. public bool ShouldAnalyzeTree(SyntaxTree tree, GeneratedCodeRecognizer generatedCodeRecognizer) => - (generatedCodeRecognizer is null || SonarLintFile().AnalyzeGeneratedCode || !tree.IsGenerated(generatedCodeRecognizer, Compilation)) - && (tree is null || !IsUnchanged(tree)); + SonarLintFile() is var sonarLintReader + && (generatedCodeRecognizer is null || sonarLintReader.AnalyzeGeneratedCode || !tree.IsGenerated(generatedCodeRecognizer, Compilation)) + && (tree is null || (!IsUnchanged(tree) && ShouldAnalyzeFile(sonarLintReader, tree.FilePath))); /// /// Reads configuration from SonarProjectConfig.xml file and caches the result for scope of this analysis. @@ -122,6 +125,27 @@ public bool HasMatchingScope(DiagnosticDescriptor descriptor) descriptor.CustomTags.Contains(tag); } + private bool ShouldAnalyzeFile(SonarLintXmlReader sonarLintXml, string filePath) => + ProjectConfiguration().ProjectType != ProjectType.Unknown // Not SonarLint context, NuGet or Scanner <= 5.0 + || (FileInclusionCache.GetValue(Compilation, _ => new()) is var cache + && cache.GetOrAdd(filePath, _ => IsFileIncluded(sonarLintXml, filePath))); + private ImmutableHashSet CreateUnchangedFilesHashSet() => ImmutableHashSet.Create(StringComparer.OrdinalIgnoreCase, ProjectConfiguration().AnalysisConfig?.UnchangedFiles() ?? Array.Empty()); + + private bool IsFileIncluded(SonarLintXmlReader sonarLintXml, string filePath) => + IsTestProject() + ? IsFileIncluded(sonarLintXml.TestInclusions, sonarLintXml.TestExclusions, sonarLintXml.GlobalTestExclusions, filePath) + : IsFileIncluded(sonarLintXml.Inclusions, sonarLintXml.Exclusions, sonarLintXml.GlobalExclusions, filePath); + + private static bool IsFileIncluded(string[] inclusions, string[] exclusions, string[] globalExclusions, string filePath) => + IsIncluded(inclusions, filePath) + && !IsExcluded(exclusions, filePath) + && !IsExcluded(globalExclusions, filePath); + + private static bool IsIncluded(string[] inclusions, string filePath) => + inclusions is { Length: 0 } || inclusions.Any(x => WildcardPatternMatcher.IsMatch(x, filePath)); + + private static bool IsExcluded(string[] exclusions, string filePath) => + exclusions.Any(x => WildcardPatternMatcher.IsMatch(x, filePath)); } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.ShouldAnalyzeTree.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.ShouldAnalyzeTree.cs index 0d1d18583c9..c667b8898e2 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.ShouldAnalyzeTree.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.ShouldAnalyzeTree.cs @@ -18,7 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System.IO; using System.Text; using Microsoft.CodeAnalysis.Text; using Moq; @@ -78,7 +77,7 @@ public void ShouldAnalyzeTree_Scanner_UnchangedFiles_ContainsOtherFile() [DataRow(OtherFileName, true)] public void ShouldAnalyzeTree_GeneratedFile_NoSonarLintXml(string fileName, bool expected) { - var sonarLintXml = CreateSonarLintXml(true); + var sonarLintXml = new DummySourceText(AnalysisScaffolding.GenerateSonarLintXmlContent(analyzeGeneratedCode: true)); var (compilation, tree) = CreateDummyCompilation(AnalyzerLanguage.CSharp, fileName); var sut = CreateSut(compilation, CreateOptions(sonarLintXml, @"ResourceTests\Foo.xml")); @@ -89,7 +88,7 @@ public void ShouldAnalyzeTree_GeneratedFile_NoSonarLintXml(string fileName, bool [TestMethod] public void ShouldAnalyzeTree_GeneratedFile_ShouldAnalyzeGeneratedProvider_IsCached() { - var sonarLintXml = CreateSonarLintXml(true); + var sonarLintXml = new DummySourceText(AnalysisScaffolding.GenerateSonarLintXmlContent(analyzeGeneratedCode: true)); var additionalText = new Mock(); additionalText.Setup(x => x.Path).Returns("SonarLint.xml"); additionalText.Setup(x => x.GetText(default)).Returns(sonarLintXml); @@ -129,7 +128,7 @@ public void ShouldAnalyzeTree_GeneratedFile_InvalidSonarLintXml(string fileName, [DataRow(OtherFileName)] public void ShouldAnalyzeTree_GeneratedFile_AnalyzeGenerated_AnalyzeAllFiles(string fileName) { - var sonarLintXml = CreateSonarLintXml(true); + var sonarLintXml = new DummySourceText(AnalysisScaffolding.GenerateSonarLintXmlContent(analyzeGeneratedCode: true)); var (compilation, tree) = CreateDummyCompilation(AnalyzerLanguage.CSharp, fileName); var sut = CreateSut(compilation, CreateOptions(sonarLintXml)); @@ -137,25 +136,24 @@ public void ShouldAnalyzeTree_GeneratedFile_AnalyzeGenerated_AnalyzeAllFiles(str } [DataTestMethod] - [DataRow(GeneratedFileName, false)] - [DataRow(OtherFileName, true)] - public void ShouldAnalyzeTree_CorrectSettingUsed(string fileName, bool expectedCSharp) + [DataRow(GeneratedFileName, LanguageNames.CSharp, false)] + [DataRow(OtherFileName, LanguageNames.CSharp, true)] + [DataRow(GeneratedFileName, LanguageNames.VisualBasic, false)] + [DataRow(OtherFileName, LanguageNames.VisualBasic, true)] + public void ShouldAnalyzeTree_CorrectSettingUsed(string fileName, string language, bool expected) { - var sonarLintXml = CreateSonarLintXml(false); - var (compilationCS, treeCS) = CreateDummyCompilation(AnalyzerLanguage.CSharp, fileName); - var (compilationVB, treeVB) = CreateDummyCompilation(AnalyzerLanguage.VisualBasic, fileName); - var sutCS = CreateSut(compilationCS, CreateOptions(sonarLintXml)); - var sutVB = CreateSut(compilationVB, CreateOptions(sonarLintXml)); - - sutCS.ShouldAnalyzeTree(treeCS, CSharpGeneratedCodeRecognizer.Instance).Should().Be(expectedCSharp); - sutVB.ShouldAnalyzeTree(treeVB, VisualBasicGeneratedCodeRecognizer.Instance).Should().BeTrue(); + var sonarLintXml = new DummySourceText(AnalysisScaffolding.GenerateSonarLintXmlContent(language: language, analyzeGeneratedCode: false)); + var analyzerLanguage = language == LanguageNames.CSharp ? AnalyzerLanguage.CSharp : AnalyzerLanguage.VisualBasic; + var (compilation, tree) = CreateDummyCompilation(analyzerLanguage, fileName); + var sut = CreateSut(compilation, CreateOptions(sonarLintXml)); + GeneratedCodeRecognizer generatedCodeRecognizer = language == LanguageNames.CSharp ? CSharpGeneratedCodeRecognizer.Instance : VisualBasicGeneratedCodeRecognizer.Instance; - sonarLintXml.ToStringCallCount.Should().Be(2, "file should be read once per language"); + sut.ShouldAnalyzeTree(tree, generatedCodeRecognizer).Should().Be(expected); + sonarLintXml.ToStringCallCount.Should().Be(1, "file should be read once per language"); // Read again to check caching - sutVB.ShouldAnalyzeTree(treeVB, VisualBasicGeneratedCodeRecognizer.Instance).Should().BeTrue(); - - sonarLintXml.ToStringCallCount.Should().Be(2, "file should not have been read again"); + sut.ShouldAnalyzeTree(tree, generatedCodeRecognizer).Should().Be(expected); + sonarLintXml.ToStringCallCount.Should().Be(1, "file should not have been read again"); } // Until https://github.com/SonarSource/sonar-dotnet/issues/2228, we were considering a file as generated if the word "generated" was contained inside a region. @@ -329,26 +327,106 @@ public class GenericAttribute : Attribute { } VerifyEmpty("test.cs", sourceCs, new CS.EmptyStatement()); } - private static DummySourceText CreateSonarLintXml(bool analyzeGeneratedCSharp) => - new($""" - - - - - dummy - false - - - sonar.cs.analyzeGeneratedCode - {analyzeGeneratedCSharp.ToString().ToLower()} - - - sonar.vbnet.analyzeGeneratedCode - true - - - - """); + [DataTestMethod] + [DataRow("Foo", new string[] { "Foo" }, ProjectType.Product, false)] + [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Product, true)] + [DataRow("Foo", new string[] { "Foo" }, ProjectType.Test, true)] + [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Test, true)] + public void ShouldAnalyzeTree_Exclusions_ReturnExpected(string filePath, string[] exclusions, ProjectType projectType, bool expectedResult) => + ShouldAnalyzeTree_WithExclusionInclusionParametersSet_ReturnsTrueForIncludedFilesOnly(filePath, projectType, expectedResult, exclusions: exclusions); + + [DataTestMethod] + [DataRow("Foo", new string[] { "Foo" }, ProjectType.Product, false)] + [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Product, true)] + [DataRow("Foo", new string[] { "Foo" }, ProjectType.Test, true)] + [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Test, true)] + public void ShouldAnalyzeTree_GlobalExclusions_ReturnExpected(string filePath, string[] globalExclusions, ProjectType projectType, bool expectedResult) => + ShouldAnalyzeTree_WithExclusionInclusionParametersSet_ReturnsTrueForIncludedFilesOnly(filePath, projectType, expectedResult, globalExclusions: globalExclusions); + + [DataTestMethod] + [DataRow("Foo", new string[] { "Foo" }, ProjectType.Product, true)] + [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Product, true)] + [DataRow("Foo", new string[] { "Foo" }, ProjectType.Test, false)] + [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Test, true)] + public void ShouldAnalyzeTree_TestExclusions_ReturnExpected(string filePath, string[] testExclusions, ProjectType projectType, bool expectedResult) => + ShouldAnalyzeTree_WithExclusionInclusionParametersSet_ReturnsTrueForIncludedFilesOnly(filePath, projectType, expectedResult, testExclusions: testExclusions); + + [DataTestMethod] + [DataRow("Foo", new string[] { "Foo" }, ProjectType.Product, true)] + [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Product, true)] + [DataRow("Foo", new string[] { "Foo" }, ProjectType.Test, false)] + [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Test, true)] + public void ShouldAnalyzeTree_GlobalTestExclusions_ReturnExpected(string filePath, string[] globalTestExclusions, ProjectType projectType, bool expectedResult) => + ShouldAnalyzeTree_WithExclusionInclusionParametersSet_ReturnsTrueForIncludedFilesOnly(filePath, projectType, expectedResult, globalTestExclusions: globalTestExclusions); + + [DataTestMethod] + [DataRow("Foo", new string[] { "Foo" }, ProjectType.Product, true)] + [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Product, false)] + [DataRow("Foo", new string[] { "Foo" }, ProjectType.Test, true)] + [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Test, true)] + public void ShouldAnalyzeTree_Inclusions_ReturnExpected(string filePath, string[] inclusions, ProjectType projectType, bool expectedResult) => + ShouldAnalyzeTree_WithExclusionInclusionParametersSet_ReturnsTrueForIncludedFilesOnly(filePath, projectType, expectedResult, inclusions: inclusions); + + [DataTestMethod] + [DataRow("Foo", new string[] { "Foo" }, ProjectType.Product, true)] + [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Product, true)] + [DataRow("Foo", new string[] { "Foo" }, ProjectType.Test, true)] + [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Test, false)] + public void ShouldAnalyzeTree_TestInclusions_ReturnExpected(string filePath, string[] testInclusions, ProjectType projectType, bool expectedResult) => + ShouldAnalyzeTree_WithExclusionInclusionParametersSet_ReturnsTrueForIncludedFilesOnly(filePath, projectType, expectedResult, testInclusions: testInclusions); + + [DataTestMethod] + [DataRow("Foo", new string[] { "Foo" }, new string[] { "Foo" }, false)] + [DataRow("Foo", new string[] { "NotFoo" }, new string[] { "Foo" }, false)] + [DataRow("Foo", new string[] { "Foo" }, new string[] { "NotFoo" }, true)] + [DataRow("Foo", new string[] { "NotFoo" }, new string[] { "NotFoo" }, false)] + public void ShouldAnalyzeTree_MixedInput_ProductProject_ReturnExpected(string filePath, string[] inclusions, string[] exclusions, bool expectedResult) => + ShouldAnalyzeTree_WithExclusionInclusionParametersSet_ReturnsTrueForIncludedFilesOnly(filePath, ProjectType.Product, expectedResult, inclusions: inclusions, exclusions: exclusions); + + [DataTestMethod] + [DataRow("Foo", new string[] { "Foo" }, new string[] { "Foo" }, false)] + [DataRow("Foo", new string[] { "NotFoo" }, new string[] { "Foo" }, false)] + [DataRow("Foo", new string[] { "Foo" }, new string[] { "NotFoo" }, true)] + [DataRow("Foo", new string[] { "NotFoo" }, new string[] { "NotFoo" }, false)] + public void ShouldAnalyzeTree_MixedInput_TestProject_ReturnExpected(string filePath, string[] testInclusions, string[] testExclusions, bool expectedResult) => + ShouldAnalyzeTree_WithExclusionInclusionParametersSet_ReturnsTrueForIncludedFilesOnly(filePath, ProjectType.Test, expectedResult, testInclusions: testInclusions, testExclusions: testExclusions); + + private void ShouldAnalyzeTree_WithExclusionInclusionParametersSet_ReturnsTrueForIncludedFilesOnly( + string fileName, + ProjectType projectType, + bool shouldAnalyze, + string language = LanguageNames.CSharp, + string[] exclusions = null, + string[] inclusions = null, + string[] globalExclusions = null, + string[] testExclusions = null, + string[] testInclusions = null, + string[] globalTestExclusions = null) + { + var analyzerLanguage = language == LanguageNames.CSharp ? AnalyzerLanguage.CSharp : AnalyzerLanguage.VisualBasic; + var sonarLintXml = AnalysisScaffolding.CreateSonarLintXml( + TestContext, + language: language, + exclusions: exclusions, + inclusions: inclusions, + globalExclusions: globalExclusions, + testExclusions: testExclusions, + testInclusions: testInclusions, + globalTestExclusions: globalTestExclusions); + var options = AnalysisScaffolding.CreateOptions(sonarLintXml); + + var compilation = SolutionBuilder + .Create() + .AddProject(analyzerLanguage, createExtraEmptyFile: false) + .AddReferences(TestHelper.ProjectTypeReference(projectType)) + .AddSnippet(string.Empty, fileName) + .GetCompilation(); + var tree = compilation.SyntaxTrees.Single(x => x.FilePath.Contains(fileName)); + var sut = CreateSut(compilation, options); + + GeneratedCodeRecognizer codeRecognizer = language == LanguageNames.CSharp ? CSharpGeneratedCodeRecognizer.Instance : VisualBasicGeneratedCodeRecognizer.Instance; + sut.ShouldAnalyzeTree(tree, codeRecognizer).Should().Be(shouldAnalyze); + } private AnalyzerOptions CreateOptions(string[] unchangedFiles) => AnalysisScaffolding.CreateOptions(AnalysisScaffolding.CreateSonarProjectConfigWithUnchangedFiles(TestContext, unchangedFiles)); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/AnalysisScaffolding.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/AnalysisScaffolding.cs index d15e6b096af..5f518397a40 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.Xml.Linq; using Microsoft.CodeAnalysis.Text; using Moq; using SonarAnalyzer.AnalysisContext; @@ -80,6 +81,45 @@ public static string CreateSonarProjectConfigWithFilesToAnalyze(TestContext cont public static string CreateSonarProjectConfig(TestContext context, ProjectType projectType, bool isScannerRun = true) => CreateSonarProjectConfig(context, "ProjectType", projectType.ToString(), isScannerRun); + public static string CreateSonarLintXml( + TestContext context, + string language = LanguageNames.CSharp, + bool analyzeGeneratedCode = false, + string[] exclusions = null, + string[] inclusions = null, + 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)); + + public static string GenerateSonarLintXmlContent( + string language = LanguageNames.CSharp, + bool analyzeGeneratedCode = false, + string[] exclusions = null, + string[] inclusions = null, + string[] globalExclusions = null, + string[] testExclusions = null, + string[] testInclusions = null, + string[] globalTestExclusions = null) => + new XDocument( + new XDeclaration("1.0", "utf-8", "yes"), + new XElement("AnalysisInput", + new XElement("Settings", + CreateSetting($"sonar.{(language == LanguageNames.CSharp ? "cs" : "vbnet")}.analyzeGeneratedCode", analyzeGeneratedCode.ToString()), + CreateSetting("sonar.exclusions", ConcatenateStringArray(exclusions)), + CreateSetting("sonar.inclusions", ConcatenateStringArray(inclusions)), + 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(); + + private static XElement CreateSetting(string key, string value) => + new("Setting", new XElement("Key", key), new XElement("Value", value)); + + private static string ConcatenateStringArray(string[] array) => + string.Join(",", array ?? Array.Empty()); + private static string CreateSonarProjectConfig(TestContext context, string element, string value, bool isScannerRun, string analysisConfigPath = null) { var sonarProjectConfigPath = TestHelper.TestPath(context, "SonarProjectConfig.xml"); From 36819f906ca0dd9ec0fb201fd4b98c92b1cf4c43 Mon Sep 17 00:00:00 2001 From: Cristian Ambrosini <114916336+cristian-ambrosini-sonarsource@users.noreply.github.com> Date: Fri, 10 Mar 2023 10:28:57 +0100 Subject: [PATCH 06/14] Use SonarLint data class to provide the rule parameters to the rules (#6888) --- .../ParametrizedDiagnosticAnalyzer.cs | 2 +- .../Helpers/ParameterLoader.cs | 85 +---------- .../Helpers/SonarLintXmlReader.cs | 28 ++-- .../Helpers/ParameterLoaderTest.cs | 139 ++++++++---------- .../Helpers/SonarLintXmlReaderTest.cs | 9 ++ .../TestFramework/AnalysisScaffolding.cs | 35 ++++- 6 files changed, 126 insertions(+), 172 deletions(-) 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()); From bc50fb056bd1080f7751187868aa2eaa596dd443 Mon Sep 17 00:00:00 2001 From: Mary Georgiou <89914005+mary-georgiou-sonarsource@users.noreply.github.com> Date: Fri, 10 Mar 2023 14:56:39 +0100 Subject: [PATCH 07/14] Add SonarLint exclusion/inclusion IT (#6884) --- ...SonarAnalyzer.Testing.ImportBefore.targets | 2 + .../config/SonarLintExclusions/SonarLint.xml | 40 +++++++++++++++++++ .../SonarLintExclusions--net7.0-S2094.json | 17 ++++++++ .../SonarLintExclusions--net7.0-S3990.json | 9 +++++ .../SonarLintExclusions--net7.0-S3992.json | 9 +++++ ...SonarLintExclusionsTest--net7.0-S2699.json | 17 ++++++++ analyzers/its/regression-test.ps1 | 3 +- .../SonarLintExclusions.sln | 36 +++++++++++++++++ .../Included/ExcludedByExclusion.cs | 4 ++ .../Included/ExcludedByExclusion2.cs | 4 ++ .../Included/ExcludedByGlobalExclusion.cs | 4 ++ .../SonarLintExclusions/Included/Included.cs | 4 ++ .../SonarLintExclusions/NotIncluded.cs | 4 ++ .../SonarLintExclusions.csproj | 8 ++++ .../IncludedTest/ExcludedByExclusionTest.cs | 10 +++++ .../ExcludedByGlobalExclusionTest.cs | 10 +++++ .../IncludedTest/IncludedTest.cs | 10 +++++ .../NotIncludedTest.cs | 10 +++++ .../SonarLintExclusionsTest.csproj | 15 +++++++ .../sources/SonarLintExclusions/global.json | 6 +++ 20 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 analyzers/its/config/SonarLintExclusions/SonarLint.xml create mode 100644 analyzers/its/expected/SonarLintExclusions/SonarLintExclusions--net7.0-S2094.json create mode 100644 analyzers/its/expected/SonarLintExclusions/SonarLintExclusions--net7.0-S3990.json create mode 100644 analyzers/its/expected/SonarLintExclusions/SonarLintExclusions--net7.0-S3992.json create mode 100644 analyzers/its/expected/SonarLintExclusions/SonarLintExclusionsTest--net7.0-S2699.json create mode 100644 analyzers/its/sources/SonarLintExclusions/SonarLintExclusions.sln create mode 100644 analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/Included/ExcludedByExclusion.cs create mode 100644 analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/Included/ExcludedByExclusion2.cs create mode 100644 analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/Included/ExcludedByGlobalExclusion.cs create mode 100644 analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/Included/Included.cs create mode 100644 analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/NotIncluded.cs create mode 100644 analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/SonarLintExclusions.csproj create mode 100644 analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/IncludedTest/ExcludedByExclusionTest.cs create mode 100644 analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/IncludedTest/ExcludedByGlobalExclusionTest.cs create mode 100644 analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/IncludedTest/IncludedTest.cs create mode 100644 analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/NotIncludedTest.cs create mode 100644 analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/SonarLintExclusionsTest.csproj create mode 100644 analyzers/its/sources/SonarLintExclusions/global.json diff --git a/analyzers/its/SonarAnalyzer.Testing.ImportBefore.targets b/analyzers/its/SonarAnalyzer.Testing.ImportBefore.targets index f3fda742c51..ea85c04a441 100644 --- a/analyzers/its/SonarAnalyzer.Testing.ImportBefore.targets +++ b/analyzers/its/SonarAnalyzer.Testing.ImportBefore.targets @@ -11,6 +11,8 @@ Product Test + + Unknown diff --git a/analyzers/its/config/SonarLintExclusions/SonarLint.xml b/analyzers/its/config/SonarLintExclusions/SonarLint.xml new file mode 100644 index 00000000000..be8726ef80a --- /dev/null +++ b/analyzers/its/config/SonarLintExclusions/SonarLint.xml @@ -0,0 +1,40 @@ + + + + + + + sonar.cs.ignoreHeaderComments + true + + + sonar.vbnet.ignoreHeaderComments + true + + + sonar.inclusions + **/Included/*.cs + + + sonar.exclusions + **/ExcludedByExclusion.cs,**/ExcludedByExclusion2.cs + + + sonar.global.exclusions + **/ExcludedByGlobalExclusion.cs + + + + sonar.test.inclusions + **/IncludedTest/*.cs + + + sonar.test.exclusions + **/ExcludedByExclusionTest.cs + + + sonar.global.test.exclusions + **/ExcludedByGlobalExclusionTest.cs + + + \ No newline at end of file diff --git a/analyzers/its/expected/SonarLintExclusions/SonarLintExclusions--net7.0-S2094.json b/analyzers/its/expected/SonarLintExclusions/SonarLintExclusions--net7.0-S2094.json new file mode 100644 index 00000000000..e86aa12d401 --- /dev/null +++ b/analyzers/its/expected/SonarLintExclusions/SonarLintExclusions--net7.0-S2094.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2094", +"message": "Remove this empty class, write its code or make it an "interface".", +"location": { +"uri": "sources\SonarLintExclusions\SonarLintExclusions\Included\Included.cs", +"region": { +"startLine": 3, +"startColumn": 18, +"endLine": 3, +"endColumn": 26 +} +} +} +] +} diff --git a/analyzers/its/expected/SonarLintExclusions/SonarLintExclusions--net7.0-S3990.json b/analyzers/its/expected/SonarLintExclusions/SonarLintExclusions--net7.0-S3990.json new file mode 100644 index 00000000000..17786f2ed3f --- /dev/null +++ b/analyzers/its/expected/SonarLintExclusions/SonarLintExclusions--net7.0-S3990.json @@ -0,0 +1,9 @@ +{ +"issues": [ +{ +"id": "S3990", +"message": "Provide a 'CLSCompliant' attribute for assembly 'SonarLintExclusions'.", +"location": null +} +] +} diff --git a/analyzers/its/expected/SonarLintExclusions/SonarLintExclusions--net7.0-S3992.json b/analyzers/its/expected/SonarLintExclusions/SonarLintExclusions--net7.0-S3992.json new file mode 100644 index 00000000000..dfd144e2a2e --- /dev/null +++ b/analyzers/its/expected/SonarLintExclusions/SonarLintExclusions--net7.0-S3992.json @@ -0,0 +1,9 @@ +{ +"issues": [ +{ +"id": "S3992", +"message": "Provide a 'ComVisible' attribute for assembly 'SonarLintExclusions'.", +"location": null +} +] +} diff --git a/analyzers/its/expected/SonarLintExclusions/SonarLintExclusionsTest--net7.0-S2699.json b/analyzers/its/expected/SonarLintExclusions/SonarLintExclusionsTest--net7.0-S2699.json new file mode 100644 index 00000000000..9a66c44306c --- /dev/null +++ b/analyzers/its/expected/SonarLintExclusions/SonarLintExclusionsTest--net7.0-S2699.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2699", +"message": "Add at least one assertion to this test case.", +"location": { +"uri": "sources\SonarLintExclusions\SonarLintExclusionsTest\IncludedTest\IncludedTest.cs", +"region": { +"startLine": 8, +"startColumn": 21, +"endLine": 8, +"endColumn": 32 +} +} +} +] +} diff --git a/analyzers/its/regression-test.ps1 b/analyzers/its/regression-test.ps1 index ad7cf9d8057..513681eedbc 100644 --- a/analyzers/its/regression-test.ps1 +++ b/analyzers/its/regression-test.ps1 @@ -12,7 +12,7 @@ param $ruleId, [Parameter(HelpMessage = "The name of single project to build. If ommited, all projects will be build.")] - [ValidateSet("AnalyzeGenerated.CS", "AnalyzeGenerated.VB", "akka.net", "Automapper", "Ember-MM", "Nancy", "NetCore31", "Net5", "Net6", "Net7", "NetCore31WithConfigurableRules" , "ManuallyAddedNoncompliantIssues.CS", "ManuallyAddedNoncompliantIssues.VB", "Roslyn.1.3.1", "SkipGenerated.CS", "SkipGenerated.VB", "WebConfig")] + [ValidateSet("AnalyzeGenerated.CS", "AnalyzeGenerated.VB", "akka.net", "Automapper", "Ember-MM", "Nancy", "NetCore31", "Net5", "Net6", "Net7", "NetCore31WithConfigurableRules" , "ManuallyAddedNoncompliantIssues.CS", "ManuallyAddedNoncompliantIssues.VB", "Roslyn.1.3.1", "SkipGenerated.CS", "SkipGenerated.VB", "SonarLintExclusions", "WebConfig")] [string] $project ) @@ -499,6 +499,7 @@ try { Build-Project-DotnetTool "NetCore31WithConfigurableRules" "NetCore31WithConfigurableRules.sln" Build-Project-DotnetTool "akka.net" "src\Akka.sln" Build-Project-DotnetTool "Automapper" "Automapper.sln" + Build-Project-DotnetTool "SonarLintExclusions" "SonarLintExclusions.sln" Write-Header "Processing analyzer results" diff --git a/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions.sln b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions.sln new file mode 100644 index 00000000000..3e5bcbbe34b --- /dev/null +++ b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions.sln @@ -0,0 +1,36 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33417.168 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SonarLintExclusions", "SonarLintExclusions\SonarLintExclusions.csproj", "{F5D0F2AC-2BED-42AA-B219-674F970E8400}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SonarLintExclusionsTest", "SonarLintExclusionsTest\SonarLintExclusionsTest.csproj", "{0F14329D-7A71-489B-BB85-2B257A866429}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1CF8B86B-6A00-4E05-931E-F278FCFDF1CF}" + ProjectSection(SolutionItems) = preProject + global.json = global.json + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F5D0F2AC-2BED-42AA-B219-674F970E8400}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5D0F2AC-2BED-42AA-B219-674F970E8400}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5D0F2AC-2BED-42AA-B219-674F970E8400}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5D0F2AC-2BED-42AA-B219-674F970E8400}.Release|Any CPU.Build.0 = Release|Any CPU + {0F14329D-7A71-489B-BB85-2B257A866429}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F14329D-7A71-489B-BB85-2B257A866429}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F14329D-7A71-489B-BB85-2B257A866429}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F14329D-7A71-489B-BB85-2B257A866429}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9FA0AE0B-19EB-4F4F-A69E-5AB50EE8240E} + EndGlobalSection +EndGlobal diff --git a/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/Included/ExcludedByExclusion.cs b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/Included/ExcludedByExclusion.cs new file mode 100644 index 00000000000..c33727affc7 --- /dev/null +++ b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/Included/ExcludedByExclusion.cs @@ -0,0 +1,4 @@ +namespace SonarLintExclusions +{ + public class ExcludedByExclusion { } // S2094 +} diff --git a/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/Included/ExcludedByExclusion2.cs b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/Included/ExcludedByExclusion2.cs new file mode 100644 index 00000000000..8366890afef --- /dev/null +++ b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/Included/ExcludedByExclusion2.cs @@ -0,0 +1,4 @@ +namespace SonarLintExclusions +{ + public class ExcludedByExclusion2 { } // S2094 +} diff --git a/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/Included/ExcludedByGlobalExclusion.cs b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/Included/ExcludedByGlobalExclusion.cs new file mode 100644 index 00000000000..8eafecd8f7f --- /dev/null +++ b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/Included/ExcludedByGlobalExclusion.cs @@ -0,0 +1,4 @@ +namespace SonarLintExclusions +{ + public class ExcludedByGlobalExclusion { } // S2094 +} diff --git a/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/Included/Included.cs b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/Included/Included.cs new file mode 100644 index 00000000000..6fba906c697 --- /dev/null +++ b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/Included/Included.cs @@ -0,0 +1,4 @@ +namespace SonarLintExclusions +{ + public class Included { } // S2094 +} diff --git a/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/NotIncluded.cs b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/NotIncluded.cs new file mode 100644 index 00000000000..95a9911ce76 --- /dev/null +++ b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/NotIncluded.cs @@ -0,0 +1,4 @@ +namespace SonarLintExclusions +{ + public class NotIncluded { } // S2094 +} diff --git a/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/SonarLintExclusions.csproj b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/SonarLintExclusions.csproj new file mode 100644 index 00000000000..15297210401 --- /dev/null +++ b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusions/SonarLintExclusions.csproj @@ -0,0 +1,8 @@ + + + + net7.0 + true + + + diff --git a/analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/IncludedTest/ExcludedByExclusionTest.cs b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/IncludedTest/ExcludedByExclusionTest.cs new file mode 100644 index 00000000000..d3fcf14795b --- /dev/null +++ b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/IncludedTest/ExcludedByExclusionTest.cs @@ -0,0 +1,10 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +namespace SonarLintExclusionsTest +{ + [TestClass] + public class ExcludedByExclusionTest + { + [TestMethod] + public void TestMethod1() { } + } +} diff --git a/analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/IncludedTest/ExcludedByGlobalExclusionTest.cs b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/IncludedTest/ExcludedByGlobalExclusionTest.cs new file mode 100644 index 00000000000..b1aadee0880 --- /dev/null +++ b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/IncludedTest/ExcludedByGlobalExclusionTest.cs @@ -0,0 +1,10 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +namespace SonarLintExclusionsTest +{ + [TestClass] + public class ExcludedByGlobalExclusionTest + { + [TestMethod] + public void TestMethod1() { } + } +} diff --git a/analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/IncludedTest/IncludedTest.cs b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/IncludedTest/IncludedTest.cs new file mode 100644 index 00000000000..cc364afdccc --- /dev/null +++ b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/IncludedTest/IncludedTest.cs @@ -0,0 +1,10 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +namespace SonarLintExclusionsTest +{ + [TestClass] + public class IncludedTest + { + [TestMethod] + public void TestMethod1() { } + } +} diff --git a/analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/NotIncludedTest.cs b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/NotIncludedTest.cs new file mode 100644 index 00000000000..8b81a00464c --- /dev/null +++ b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/NotIncludedTest.cs @@ -0,0 +1,10 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +namespace SonarLintExclusionsTest +{ + [TestClass] + public class NotIncludedTest + { + [TestMethod] + public void TestMethod1() { } + } +} diff --git a/analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/SonarLintExclusionsTest.csproj b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/SonarLintExclusionsTest.csproj new file mode 100644 index 00000000000..0d99fb1aff7 --- /dev/null +++ b/analyzers/its/sources/SonarLintExclusions/SonarLintExclusionsTest/SonarLintExclusionsTest.csproj @@ -0,0 +1,15 @@ + + + + net7.0 + false + true + + + + + + + + + diff --git a/analyzers/its/sources/SonarLintExclusions/global.json b/analyzers/its/sources/SonarLintExclusions/global.json new file mode 100644 index 00000000000..4037c7755e7 --- /dev/null +++ b/analyzers/its/sources/SonarLintExclusions/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "7.0.100", + "rollForward": "latestMinor" + } +} \ No newline at end of file From bbefe664f4f126ef31c756ea23ec6b832090d544 Mon Sep 17 00:00:00 2001 From: Cristian Ambrosini <114916336+cristian-ambrosini-sonarsource@users.noreply.github.com> Date: Mon, 13 Mar 2023 11:19:50 +0100 Subject: [PATCH 08/14] UTs: Cleanup ResourceTest folder (#6891) --- .../Helpers/ParameterLoaderTest.cs | 16 +- .../Helpers/SonarLintXmlReaderTest.cs | 2 +- .../Helpers/SonarLintXmlTest.cs | 24 +- .../AnalyzeGeneratedFalse/SonarLint.xml | 9 - .../AnalyzeGeneratedFalseVbnet/SonarLint.xml | 9 - .../AnalyzeGeneratedTrue/SonarLint.xml | 9 - .../AnalyzeGeneratedTrueVbnet/SonarLint.xml | 9 - .../SonarLint.xml | 9 - .../SonarLint.xml | 9 - .../SonarLint.xml | 9 - .../SonarLint.xml | 9 - .../ResourceTests/SonarLint.xml | 30 - .../All_properties_cs/SonarLint.xml | 822 +----------------- .../SonarLint.xml | 81 -- .../All_properties_vbnet/SonarLint.xml | 808 ----------------- .../RuleWithBooleanParameter/SonarLint.xml | 0 .../RuleWithStringParameter/SonarLint.xml | 0 .../StringInsteadOfBoolean/SonarLint.xml | 0 .../StringInsteadOfInt/SonarLint.xml | 0 .../ResourceTests/ToChange/SonarLint.xml | 30 - .../Utilities/UtilityAnalyzerBaseTest.cs | 33 +- .../TestFramework/AnalysisScaffolding.cs | 5 +- 22 files changed, 48 insertions(+), 1875 deletions(-) delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/AnalyzeGeneratedFalse/SonarLint.xml delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/AnalyzeGeneratedFalseVbnet/SonarLint.xml delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/AnalyzeGeneratedTrue/SonarLint.xml delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/AnalyzeGeneratedTrueVbnet/SonarLint.xml delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/IgnoreHeaderCommentsFalseCSharp/SonarLint.xml delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/IgnoreHeaderCommentsFalseVbnet/SonarLint.xml delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/IgnoreHeaderCommentsTrueCSharp/SonarLint.xml delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/IgnoreHeaderCommentsTrueVbnet/SonarLint.xml delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLint.xml delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_small_template/SonarLint.xml rename analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/{ => SonarLintXml}/RuleWithBooleanParameter/SonarLint.xml (100%) rename analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/{ => SonarLintXml}/RuleWithStringParameter/SonarLint.xml (100%) rename analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/{ => SonarLintXml}/StringInsteadOfBoolean/SonarLint.xml (100%) rename analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/{ => SonarLintXml}/StringInsteadOfInt/SonarLint.xml (100%) delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/ToChange/SonarLint.xml diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs index bcf46ee97b6..5ed1026cd73 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs @@ -37,7 +37,7 @@ public class ParameterLoaderTest public void SetParameterValues_WhenNoSonarLintIsGiven_DoesNotPopulateParameters(string filePath) { // Arrange - var compilation = CreateCompilationWithOption(filePath, SourceText.From(File.ReadAllText("ResourceTests\\SonarLint.xml"))); + var compilation = CreateCompilationWithOption(filePath, SourceText.From(File.ReadAllText("ResourceTests\\SonarLintXml\\All_properties_cs\\SonarLint.xml"))); var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act @@ -53,7 +53,7 @@ public void SetParameterValues_WhenNoSonarLintIsGiven_DoesNotPopulateParameters( public void SetParameterValues_WhenGivenValidSonarLintFilePath_PopulatesProperties(string filePath) { // Arrange - var compilation = CreateCompilationWithOption(filePath, SourceText.From(File.ReadAllText("ResourceTests\\SonarLint.xml"))); + var compilation = CreateCompilationWithOption(filePath, SourceText.From(File.ReadAllText("ResourceTests\\SonarLintXml\\All_properties_cs\\SonarLint.xml"))); var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act @@ -67,7 +67,7 @@ public void SetParameterValues_WhenGivenValidSonarLintFilePath_PopulatesProperti public void SetParameterValues_WhenGivenSonarLintFileHasIntParameterType_PopulatesProperties() { // Arrange - var compilation = CreateCompilationWithOption("ResourceTests\\SonarLint.xml"); + var compilation = CreateCompilationWithOption("ResourceTests\\SonarLintXml\\All_properties_cs\\SonarLint.xml"); var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act @@ -81,7 +81,7 @@ public void SetParameterValues_WhenGivenSonarLintFileHasIntParameterType_Populat public void SetParameterValues_WhenGivenSonarLintFileHasStringParameterType_OnlyOneParameter_PopulatesProperty() { // Arrange - var compilation = CreateCompilationWithOption("ResourceTests\\RuleWithStringParameter\\SonarLint.xml"); + var compilation = CreateCompilationWithOption("ResourceTests\\SonarLintXml\\RuleWithStringParameter\\SonarLint.xml"); var analyzer = new EnumNameShouldFollowRegex(); // Cannot use mock because we use reflection to find properties. // Act @@ -95,7 +95,7 @@ public void SetParameterValues_WhenGivenSonarLintFileHasStringParameterType_Only public void SetParameterValues_WhenGivenSonarLintFileHasBooleanParameterType_OnlyOneParameter_PopulatesProperty() { // Arrange - var compilation = CreateCompilationWithOption("ResourceTests\\RuleWithBooleanParameter\\SonarLint.xml"); + var compilation = CreateCompilationWithOption("ResourceTests\\SonarLintXml\\RuleWithBooleanParameter\\SonarLint.xml"); var analyzer = new CheckFileLicense(); // Cannot use mock because we use reflection to find properties. // Act @@ -109,7 +109,7 @@ public void SetParameterValues_WhenGivenSonarLintFileHasBooleanParameterType_Onl public void SetParameterValues_WhenGivenValidSonarLintFileAndDoesNotContainAnalyzerParameters_DoesNotPopulateProperties() { // Arrange - var compilation = CreateCompilationWithOption("ResourceTests\\SonarLint.xml"); + var compilation = CreateCompilationWithOption("ResourceTests\\SonarLintXml\\All_properties_cs\\SonarLint.xml"); var analyzer = new LineLength(); // Cannot use mock because we use reflection to find properties. // Act @@ -180,7 +180,7 @@ public void SetParameterValues_WithMalformedXml_DoesNotPopulateProperties(string public void SetParameterValues_WithWrongPropertyType_StringInsteadOfInt_DoesNotPopulateProperties() { // Arrange - var compilation = CreateCompilationWithOption("ResourceTests\\StringInsteadOfInt\\SonarLint.xml"); + var compilation = CreateCompilationWithOption("ResourceTests\\SonarLintXml\\StringInsteadOfInt\\SonarLint.xml"); var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act @@ -194,7 +194,7 @@ public void SetParameterValues_WithWrongPropertyType_StringInsteadOfInt_DoesNotP public void SetParameterValues_WithWrongPropertyType_StringInsteadOfBoolean_DoesNotPopulateProperties() { // Arrange - var compilation = CreateCompilationWithOption("ResourceTests\\StringInsteadOfBoolean\\SonarLint.xml"); + var compilation = CreateCompilationWithOption("ResourceTests\\SonarLintXml\\StringInsteadOfBoolean\\SonarLint.xml"); var analyzer = new CheckFileLicense(); // Cannot use mock because we use reflection to find properties. // Act diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs index 737148185f8..009508344c2 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs @@ -41,7 +41,7 @@ public void SonarLintXmlReader_WhenAllValuesAreSet_ExpectedValues(string languag AssertArrayContent(sut.TestInclusions, nameof(sut.TestInclusions)); AssertArrayContent(sut.GlobalTestExclusions, nameof(sut.GlobalTestExclusions)); - sut.ParametrizedRules.Should().HaveCount(8); + sut.ParametrizedRules.Should().HaveCount(2); 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})?$"); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlTest.cs index b27c8f4b38d..47c834dbe53 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlTest.cs @@ -30,7 +30,7 @@ public class SonarLintXmlTest public void SonarLintXml_DeserializeFile_ExpectedValues() { var deserializer = new XmlSerializer(typeof(SonarLintXml)); - using TextReader textReader = new StreamReader("ResourceTests\\SonarLintXml\\All_properties_small_template\\SonarLint.xml"); + using TextReader textReader = new StreamReader("ResourceTests\\SonarLintXml\\All_properties_cs\\SonarLint.xml"); var sonarLintXml = (SonarLintXml)deserializer.Deserialize(textReader); AssertSettings(sonarLintXml.Settings); @@ -58,20 +58,20 @@ private static void AssertRules(List rules) rules.Should().HaveCount(4); rules.Where(x => x.Parameters.Any()).Should().HaveCount(2); - rules[0].Key.Should().BeEquivalentTo("S0001"); + rules[0].Key.Should().BeEquivalentTo("S2225"); rules[0].Parameters.Should().BeEmpty(); - rules[1].Key.Should().BeEquivalentTo("S0002"); - rules[1].Parameters.Should().BeEmpty(); - rules[2].Key.Should().BeEquivalentTo("S0003"); - rules[2].Parameters.Should().HaveCount(2); - AssertKeyValuePair(rules[2].Parameters[0], "format", "^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$"); - AssertKeyValuePair(rules[2].Parameters[1], "flagsAttributeFormat", "^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?s$"); + rules[1].Key.Should().BeEquivalentTo("S2342"); + rules[1].Parameters.Should().HaveCount(2); + AssertKeyValuePair(rules[1].Parameters[0], "format", "^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$"); + AssertKeyValuePair(rules[1].Parameters[1], "flagsAttributeFormat", "^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?s$"); - rules[3].Key.Should().BeEquivalentTo("S0004"); - rules[3].Parameters.Should().HaveCount(2); - AssertKeyValuePair(rules[3].Parameters[0], "threshold", "15"); - AssertKeyValuePair(rules[3].Parameters[1], "propertyThreshold", "3"); + rules[2].Key.Should().BeEquivalentTo("S2346"); + rules[2].Parameters.Should().BeEmpty(); + + rules[3].Key.Should().BeEquivalentTo("S1067"); + rules[3].Parameters.Should().HaveCount(1); + AssertKeyValuePair(rules[3].Parameters[0], "max", "1"); } private static void AssertKeyValuePair(SonarLintXmlKeyValuePair pair, string expectedKey, string expectedValue) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/AnalyzeGeneratedFalse/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/AnalyzeGeneratedFalse/SonarLint.xml deleted file mode 100644 index 06ff5bc2533..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/AnalyzeGeneratedFalse/SonarLint.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - sonar.cs.analyzeGeneratedCode - false - - - diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/AnalyzeGeneratedFalseVbnet/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/AnalyzeGeneratedFalseVbnet/SonarLint.xml deleted file mode 100644 index a3e4d99a79d..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/AnalyzeGeneratedFalseVbnet/SonarLint.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - sonar.vbnet.analyzeGeneratedCode - false - - - diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/AnalyzeGeneratedTrue/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/AnalyzeGeneratedTrue/SonarLint.xml deleted file mode 100644 index 058877ce0c4..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/AnalyzeGeneratedTrue/SonarLint.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - sonar.cs.analyzeGeneratedCode - true - - - diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/AnalyzeGeneratedTrueVbnet/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/AnalyzeGeneratedTrueVbnet/SonarLint.xml deleted file mode 100644 index 60d17ee38f1..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/AnalyzeGeneratedTrueVbnet/SonarLint.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - sonar.vbnet.analyzeGeneratedCode - true - - - diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/IgnoreHeaderCommentsFalseCSharp/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/IgnoreHeaderCommentsFalseCSharp/SonarLint.xml deleted file mode 100644 index 4656ffb30d8..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/IgnoreHeaderCommentsFalseCSharp/SonarLint.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - sonar.cs.ignoreHeaderComments - false - - - diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/IgnoreHeaderCommentsFalseVbnet/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/IgnoreHeaderCommentsFalseVbnet/SonarLint.xml deleted file mode 100644 index 19364407ace..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/IgnoreHeaderCommentsFalseVbnet/SonarLint.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - sonar.vbnet.ignoreHeaderComments - false - - - diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/IgnoreHeaderCommentsTrueCSharp/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/IgnoreHeaderCommentsTrueCSharp/SonarLint.xml deleted file mode 100644 index b9a6871be55..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/IgnoreHeaderCommentsTrueCSharp/SonarLint.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - sonar.cs.ignoreHeaderComments - true - - - diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/IgnoreHeaderCommentsTrueVbnet/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/IgnoreHeaderCommentsTrueVbnet/SonarLint.xml deleted file mode 100644 index 89a7cfce399..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/IgnoreHeaderCommentsTrueVbnet/SonarLint.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - sonar.vbnet.ignoreHeaderComments - true - - - diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLint.xml deleted file mode 100644 index b99df9c60f6..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLint.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - sonar.cs.ignoreHeaderComments - true - - - sonar.cs.analyzeGeneratedCode - false - - - sonar.cs.file.suffixes - .cs - - - - - S1067 - - - max - 1 - - - - - - - diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_cs/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_cs/SonarLint.xml index 0dc284bc00d..54a1ed9e464 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_cs/SonarLint.xml +++ b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_cs/SonarLint.xml @@ -46,36 +46,6 @@ S2225 - - S2346 - - - S2589 - - - S3433 - - - S1135 - - - S2223 - - - S2344 - - - S2345 - - - S4524 - - - S1134 - - - S2222 - S2342 @@ -90,799 +60,17 @@ - S2115 - - - S2583 - - - S3447 - - - S2234 - - - S2479 - - - S3444 - - - S3445 - - - S1144 - - - S1264 - - - S2114 - - - S3442 - - - S3443 - - - S3329 - - - S3440 - - - S3449 - - - S3655 - - - S3776 - - - threshold - 15 - - - propertyThreshold - 3 - - - - - S3897 - - - S2201 - - - S2688 - - - S4502 - - - S1110 - - - S1117 - - - S1118 - - - S2328 - - - S2681 - - - S2326 - - - S3415 - - - S4507 - - - S1116 - - - S1125 + S2346 - - S1479 + + S1067 - maximum - 30 + max + 1 - - S2699 - - - S4635 - - - S1123 - - - S2696 - - - S1121 - - - S2692 - - - S1006 - - - S1481 - - - S2219 - - - S3237 - - - S3427 - - - S3236 - - - S3358 - - - S3598 - - - S2386 - - - S3597 - - - S4200 - - - S5773 - - - S1172 - - - S4201 - - - S5659 - - - S3249 - - - S4456 - - - S4457 - - - S3005 - - - S3246 - - - S3247 - - - S5547 - - - S3244 - - - S4211 - - - S5542 - - - S1066 - - - S4210 - - - S1185 - - - S1186 - - - S2275 - - - S2368 - - - S3241 - - - S3457 - - - S3458 - - - S4423 - - - S1155 - - - S2245 - - - S3453 - - - S3456 - - - S4426 - - - S2123 - - - S2365 - - - S2486 - - - S3451 - - - S3330 - - - S4428 - - - S5753 - - - S3217 - - - S3218 - - - S3459 - - - S927 - - - S3450 - - - S5766 - - - S1048 - - - S1168 - - - S2259 - - - S3466 - - - S2257 - - - S3343 - - - S3346 - - - S3464 - - - S2376 - - - S3220 - - - S4433 - - - S1163 - - - S2252 - - - S4790 - - - S818 - - - S2251 - - - S2372 - - - S2743 - - - S4792 - - - S1656 - - - S907 - - - S2995 - - - S3600 - - - S2996 - - - S3963 - - - S2755 - - - S2757 - - - S3604 - - - S2997 - - - S3603 - - - S3966 - - - S1751 - - - S1871 - - - S1643 - - - S1764 - - - S2737 - - - S2971 - - - S2612 - - - S2857 - - - S3875 - - - S3871 - - - S1210 - - - S1450 - - - S2306 - - - S3877 - - - S3998 - - - S1104 - - - S1215 - - - S1699 - - - S3887 - - - S3400 - - - S3884 - - - S3885 - - - S2551 - - - S3881 - - - S2436 - - - maxMethod - 3 - - - max - 2 - - - - - S3889 - - - S1313 - - - S2437 - - - S3972 - - - S2761 - - - S3610 - - - S3973 - - - S3971 - - - S3984 - - - S4830 - - - S3981 - - - S1206 - - - S3626 - - - S3869 - - - S1940 - - - S1944 - - - S1939 - - - S1905 - - - S5034 - - - S4061 - - - S5042 - - - S1854 - - - S4070 - - - S1862 - - - S3925 - - - S3926 - - - S3927 - - - S3928 - - - S2953 - - - S3923 - - - S125 - - - S1607 - - - S1848 - - - S2930 - - - S2933 - - - S3903 - - - S3904 - - - S2934 - - - S6422 - - - S110 - - - max - 5 - - - - - S2068 - - - credentialWords - password, passwd, pwd, passphrase - - - - - S5332 - - - S112 - - - S6424 - - - S2187 - - - S3397 - - - S4487 - - - S2184 - - - S6420 - - - S2183 - - - S5693 - - - fileUploadSizeLimit - 8000000 - - - - - S107 - - - max - 7 - - - - - S108 - - - S3169 - - - S4019 - - - S3168 - - - S4015 - - - S4136 - - - S2077 - - - S2190 - - - S1199 - - - S3376 - - - S3011 - - - S3256 - - - S1075 - - - S4581 - - - S4586 - - - S3010 - - - S3251 - - - S4220 - - - S4583 - - - S2178 - - - S3264 - - - S3267 - - - S5443 - - - S101 - - - S3265 - - - S5445 - - - S3262 - - - S6419 - - - S2053 - - - S3263 - - - S2292 - - - S3260 - - - S3261 - - - S2290 - - - S2291 - - - S4144 - - - S6444 - - - S3172 - - - S4143 - - - S4159 - - - S4260 - - - S4036 - - - S4158 - - - S4277 - - - S4035 - - - S4275 - - - S2092 - - - S3060 - - - S5122 - diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_small_template/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_small_template/SonarLint.xml deleted file mode 100644 index 70ca0115566..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_small_template/SonarLint.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - sonar.cs.ignoreHeaderComments - true - - - sonar.cs.analyzeGeneratedCode - false - - - sonar.cs.file.suffixes - .cs - - - sonar.cs.roslyn.ignoreIssues - false - - - sonar.exclusions - Fake/Exclusions/**/*,Fake/Exclusions/Second*/**/* - - - sonar.inclusions - Fake/Inclusions/**/*,Fake/Inclusions/Second*/**/* - - - sonar.global.exclusions - Fake/GlobalExclusions/**/*,Fake/GlobalExclusions/Second*/**/* - - - sonar.test.exclusions - Fake/TestExclusions/**/*,Fake/TestExclusions/Second*/**/* - - - sonar.test.inclusions - Fake/TestInclusions/**/*,Fake/TestInclusions/Second*/**/* - - - sonar.global.test.exclusions - Fake/GlobalTestExclusions/**/*,Fake/GlobalTestExclusions/Second*/**/* - - - - - S0001 - - - S0002 - - - S0003 - - - format - ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$ - - - flagsAttributeFormat - ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?s$ - - - - - S0004 - - - threshold - 15 - - - propertyThreshold - 3 - - - - - - - diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_vbnet/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_vbnet/SonarLint.xml index f848061efac..270c5c95dd8 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_vbnet/SonarLint.xml +++ b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_vbnet/SonarLint.xml @@ -46,36 +46,6 @@ S2225 - - S2346 - - - S2589 - - - S3433 - - - S1135 - - - S2223 - - - S2344 - - - S2345 - - - S4524 - - - S1134 - - - S2222 - S2342 @@ -92,51 +62,6 @@ S2115 - - S2583 - - - S3447 - - - S2234 - - - S2479 - - - S3444 - - - S3445 - - - S1144 - - - S1264 - - - S2114 - - - S3442 - - - S3443 - - - S3329 - - - S3440 - - - S3449 - - - S3655 - S3776 @@ -150,739 +75,6 @@ - - S3897 - - - S2201 - - - S2688 - - - S4502 - - - S1110 - - - S1117 - - - S1118 - - - S2328 - - - S2681 - - - S2326 - - - S3415 - - - S4507 - - - S1116 - - - S1125 - - - S1479 - - - maximum - 30 - - - - - S2699 - - - S4635 - - - S1123 - - - S2696 - - - S1121 - - - S2692 - - - S1006 - - - S1481 - - - S2219 - - - S3237 - - - S3427 - - - S3236 - - - S3358 - - - S3598 - - - S2386 - - - S3597 - - - S4200 - - - S5773 - - - S1172 - - - S4201 - - - S5659 - - - S3249 - - - S4456 - - - S4457 - - - S3005 - - - S3246 - - - S3247 - - - S5547 - - - S3244 - - - S4211 - - - S5542 - - - S1066 - - - S4210 - - - S1185 - - - S1186 - - - S2275 - - - S2368 - - - S3241 - - - S3457 - - - S3458 - - - S4423 - - - S1155 - - - S2245 - - - S3453 - - - S3456 - - - S4426 - - - S2123 - - - S2365 - - - S2486 - - - S3451 - - - S3330 - - - S4428 - - - S5753 - - - S3217 - - - S3218 - - - S3459 - - - S927 - - - S3450 - - - S5766 - - - S1048 - - - S1168 - - - S2259 - - - S3466 - - - S2257 - - - S3343 - - - S3346 - - - S3464 - - - S2376 - - - S3220 - - - S4433 - - - S1163 - - - S2252 - - - S4790 - - - S818 - - - S2251 - - - S2372 - - - S2743 - - - S4792 - - - S1656 - - - S907 - - - S2995 - - - S3600 - - - S2996 - - - S3963 - - - S2755 - - - S2757 - - - S3604 - - - S2997 - - - S3603 - - - S3966 - - - S1751 - - - S1871 - - - S1643 - - - S1764 - - - S2737 - - - S2971 - - - S2612 - - - S2857 - - - S3875 - - - S3871 - - - S1210 - - - S1450 - - - S2306 - - - S3877 - - - S3998 - - - S1104 - - - S1215 - - - S1699 - - - S3887 - - - S3400 - - - S3884 - - - S3885 - - - S2551 - - - S3881 - - - S2436 - - - maxMethod - 3 - - - max - 2 - - - - - S3889 - - - S1313 - - - S2437 - - - S3972 - - - S2761 - - - S3610 - - - S3973 - - - S3971 - - - S3984 - - - S4830 - - - S3981 - - - S1206 - - - S3626 - - - S3869 - - - S1940 - - - S1944 - - - S1939 - - - S1905 - - - S5034 - - - S4061 - - - S5042 - - - S1854 - - - S4070 - - - S1862 - - - S3925 - - - S3926 - - - S3927 - - - S3928 - - - S2953 - - - S3923 - - - S125 - - - S1607 - - - S1848 - - - S2930 - - - S2933 - - - S3903 - - - S3904 - - - S2934 - - - S6422 - - - S110 - - - max - 5 - - - - - S2068 - - - credentialWords - password, passwd, pwd, passphrase - - - - - S5332 - - - S112 - - - S6424 - - - S2187 - - - S3397 - - - S4487 - - - S2184 - - - S6420 - - - S2183 - - - S5693 - - - fileUploadSizeLimit - 8000000 - - - - - S107 - - - max - 7 - - - - - S108 - - - S3169 - - - S4019 - - - S3168 - - - S4015 - - - S4136 - - - S2077 - - - S2190 - - - S1199 - - - S3376 - - - S3011 - - - S3256 - - - S1075 - - - S4581 - - - S4586 - - - S3010 - - - S3251 - - - S4220 - - - S4583 - - - S2178 - - - S3264 - - - S3267 - - - S5443 - - - S101 - - - S3265 - - - S5445 - - - S3262 - - - S6419 - - - S2053 - - - S3263 - - - S2292 - - - S3260 - - - S3261 - - - S2290 - - - S2291 - - - S4144 - - - S6444 - - - S3172 - - - S4143 - - - S4159 - - - S4260 - - - S4036 - - - S4158 - - - S4277 - - - S4035 - - - S4275 - - - S2092 - - - S3060 - - - S5122 - diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/RuleWithBooleanParameter/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/RuleWithBooleanParameter/SonarLint.xml similarity index 100% rename from analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/RuleWithBooleanParameter/SonarLint.xml rename to analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/RuleWithBooleanParameter/SonarLint.xml diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/RuleWithStringParameter/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/RuleWithStringParameter/SonarLint.xml similarity index 100% rename from analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/RuleWithStringParameter/SonarLint.xml rename to analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/RuleWithStringParameter/SonarLint.xml diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/StringInsteadOfBoolean/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/StringInsteadOfBoolean/SonarLint.xml similarity index 100% rename from analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/StringInsteadOfBoolean/SonarLint.xml rename to analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/StringInsteadOfBoolean/SonarLint.xml diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/StringInsteadOfInt/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/StringInsteadOfInt/SonarLint.xml similarity index 100% rename from analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/StringInsteadOfInt/SonarLint.xml rename to analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/StringInsteadOfInt/SonarLint.xml diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/ToChange/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/ToChange/SonarLint.xml deleted file mode 100644 index b99df9c60f6..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/ToChange/SonarLint.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - sonar.cs.ignoreHeaderComments - true - - - sonar.cs.analyzeGeneratedCode - false - - - sonar.cs.file.suffixes - .cs - - - - - S1067 - - - max - 1 - - - - - - - diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs index 8f03a14688f..d1e6b4e61b2 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs @@ -34,6 +34,7 @@ public class UtilityAnalyzerBaseTest { private const string DefaultSonarProjectConfig = @"ResourceTests\SonarProjectConfig\Path_Windows\SonarProjectConfig.xml"; private const string DefaultProjectOutFolderPath = @"ResourceTests\ProjectOutFolderPath.txt"; + public TestContext TestContext { get; set; } [DataTestMethod] [DataRow(LanguageNames.CSharp, DefaultProjectOutFolderPath, @"path\output-cs")] @@ -43,7 +44,7 @@ public class UtilityAnalyzerBaseTest public void ReadConfig_OutPath(string language, string additionalPath, string expectedOutPath) { // We do not test what is read from the SonarLint file, but we need it - var utilityAnalyzer = new TestUtilityAnalyzer(language, @"ResourceTests\SonarLint.xml", additionalPath); + var utilityAnalyzer = new TestUtilityAnalyzer(language, @"ResourceTests\SonarLintXml\All_properties_cs\SonarLint.xml", additionalPath); utilityAnalyzer.TestOutPath.Should().Be(expectedOutPath); utilityAnalyzer.TestIsAnalyzerEnabled.Should().BeTrue(); @@ -55,35 +56,37 @@ public void ReadConfig_OutPath(string language, string additionalPath, string ex public void ReadConfig_OutPath_FromSonarProjectConfig_HasPriority(string firstFile, string secondFile) { // We do not test what is read from the SonarLint file, but we need it - var utilityAnalyzer = new TestUtilityAnalyzer(LanguageNames.CSharp, @"ResourceTests\SonarLint.xml", firstFile, secondFile); + var utilityAnalyzer = new TestUtilityAnalyzer(LanguageNames.CSharp, @"ResourceTests\SonarLintXml\All_properties_cs\SonarLint.xml", firstFile, secondFile); utilityAnalyzer.TestOutPath.Should().Be(@"C:\foo\bar\.sonarqube\out\0\output-cs"); utilityAnalyzer.TestIsAnalyzerEnabled.Should().BeTrue(); } [DataTestMethod] - [DataRow(LanguageNames.CSharp, @"ResourceTests\AnalyzeGeneratedTrue\SonarLint.xml", true)] - [DataRow(LanguageNames.CSharp, @"ResourceTests\AnalyzeGeneratedFalse\SonarLint.xml", false)] - [DataRow(LanguageNames.VisualBasic, @"ResourceTests\AnalyzeGeneratedTrueVbnet\SonarLint.xml", true)] - [DataRow(LanguageNames.VisualBasic, @"ResourceTests\AnalyzeGeneratedFalseVbnet\SonarLint.xml", false)] - public void ReadsSettings_AnalyzeGenerated(string language, string sonarLintXmlPath, bool expectedAnalyzeGeneratedCodeValue) + [DataRow(LanguageNames.CSharp, true)] + [DataRow(LanguageNames.CSharp, false)] + [DataRow(LanguageNames.VisualBasic, true)] + [DataRow(LanguageNames.VisualBasic, false)] + public void ReadsSettings_AnalyzeGenerated(string language, bool analyzeGenerated) { + var sonarLintXmlPath = AnalysisScaffolding.CreateSonarLintXml(TestContext, language: language, analyzeGeneratedCode: analyzeGenerated); var utilityAnalyzer = new TestUtilityAnalyzer(language, sonarLintXmlPath, DefaultSonarProjectConfig); - utilityAnalyzer.TestAnalyzeGeneratedCode.Should().Be(expectedAnalyzeGeneratedCodeValue); + utilityAnalyzer.TestAnalyzeGeneratedCode.Should().Be(analyzeGenerated); utilityAnalyzer.TestIsAnalyzerEnabled.Should().BeTrue(); } [DataTestMethod] - [DataRow(LanguageNames.CSharp, @"ResourceTests\IgnoreHeaderCommentsTrueCSharp\SonarLint.xml", true)] - [DataRow(LanguageNames.CSharp, @"ResourceTests\IgnoreHeaderCommentsFalseCSharp\SonarLint.xml", false)] - [DataRow(LanguageNames.VisualBasic, @"ResourceTests\IgnoreHeaderCommentsTrueVbnet\SonarLint.xml", true)] - [DataRow(LanguageNames.VisualBasic, @"ResourceTests\IgnoreHeaderCommentsFalseVbnet\SonarLint.xml", false)] - public void ReadsSettings_IgnoreHeaderComments(string language, string sonarLintXmlPath, bool expectedIgnoreHeaderComments) + [DataRow(LanguageNames.CSharp, true)] + [DataRow(LanguageNames.CSharp, false)] + [DataRow(LanguageNames.VisualBasic, true)] + [DataRow(LanguageNames.VisualBasic, false)] + public void ReadsSettings_IgnoreHeaderComments(string language, bool ignoreHeaderComments) { + var sonarLintXmlPath = AnalysisScaffolding.CreateSonarLintXml(TestContext, language: language, ignoreHeaderComments: ignoreHeaderComments); var utilityAnalyzer = new TestUtilityAnalyzer(language, sonarLintXmlPath, DefaultSonarProjectConfig); - utilityAnalyzer.TestIgnoreHeaderComments.Should().Be(expectedIgnoreHeaderComments); + utilityAnalyzer.TestIgnoreHeaderComments.Should().Be(ignoreHeaderComments); utilityAnalyzer.TestIsAnalyzerEnabled.Should().BeTrue(); } @@ -96,7 +99,7 @@ public void NoSonarLintXml_AnalyzerNotEnabled() [TestMethod] public void NoOutputPath_AnalyzerNotEnabled() => - new TestUtilityAnalyzer(LanguageNames.CSharp, @"ResourceTests\AnalyzeGeneratedTrue\SonarLint.xml").TestIsAnalyzerEnabled.Should().BeFalse(); + new TestUtilityAnalyzer(LanguageNames.CSharp, AnalysisScaffolding.CreateSonarLintXml(TestContext, analyzeGeneratedCode: true)).TestIsAnalyzerEnabled.Should().BeFalse(); [TestMethod] public void GetTextRange() diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/AnalysisScaffolding.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/AnalysisScaffolding.cs index aa476e1131e..6c6b8ef5e42 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/AnalysisScaffolding.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/AnalysisScaffolding.cs @@ -86,6 +86,7 @@ public static string CreateSonarProjectConfigWithFilesToAnalyze(TestContext cont TestContext context, string language = LanguageNames.CSharp, bool analyzeGeneratedCode = false, + bool ignoreHeaderComments = false, string[] exclusions = null, string[] inclusions = null, string[] globalExclusions = null, @@ -93,11 +94,12 @@ public static string CreateSonarProjectConfigWithFilesToAnalyze(TestContext cont string[] testInclusions = null, string[] globalTestExclusions = null, List rulesParameters = null) => - TestHelper.WriteFile(context, "SonarLint.xml", GenerateSonarLintXmlContent(language, analyzeGeneratedCode, exclusions, inclusions, globalExclusions, testExclusions, testInclusions, globalTestExclusions, rulesParameters)); + TestHelper.WriteFile(context, "SonarLint.xml", GenerateSonarLintXmlContent(language, analyzeGeneratedCode, ignoreHeaderComments, exclusions, inclusions, globalExclusions, testExclusions, testInclusions, globalTestExclusions, rulesParameters)); public static string GenerateSonarLintXmlContent( string language = LanguageNames.CSharp, bool analyzeGeneratedCode = false, + bool ignoreHeaderComments = false, string[] exclusions = null, string[] inclusions = null, string[] globalExclusions = null, @@ -110,6 +112,7 @@ public static string CreateSonarProjectConfigWithFilesToAnalyze(TestContext cont new XElement("AnalysisInput", new XElement("Settings", CreateSetting($"sonar.{(language == LanguageNames.CSharp ? "cs" : "vbnet")}.analyzeGeneratedCode", analyzeGeneratedCode.ToString()), + CreateSetting($"sonar.{(language == LanguageNames.CSharp ? "cs" : "vbnet")}.ignoreHeaderComments", ignoreHeaderComments.ToString()), CreateSetting("sonar.exclusions", ConcatenateStringArray(exclusions)), CreateSetting("sonar.inclusions", ConcatenateStringArray(inclusions)), CreateSetting("sonar.global.exclusions", ConcatenateStringArray(globalExclusions)), From e841ab63a549b4c61761107dbb1e97f2d0f6f74e Mon Sep 17 00:00:00 2001 From: Cristian Ambrosini <114916336+cristian-ambrosini-sonarsource@users.noreply.github.com> Date: Mon, 13 Mar 2023 15:48:00 +0100 Subject: [PATCH 09/14] UTs: Cleanup ResourceTest folder 2/2 (#6907) --- .../sources/AnalyzeGenerated.CS/SonarLint.xml | 30 ---------- .../sources/SkipGenerated.CS/SonarLint.xml | 30 ---------- .../Helpers/ParameterLoaderTest.cs | 56 ++++++++++++++----- .../RuleWithBooleanParameter/SonarLint.xml | 30 ---------- .../RuleWithStringParameter/SonarLint.xml | 30 ---------- .../StringInsteadOfBoolean/SonarLint.xml | 30 ---------- .../StringInsteadOfInt/SonarLint.xml | 30 ---------- 7 files changed, 42 insertions(+), 194 deletions(-) delete mode 100644 analyzers/its/sources/AnalyzeGenerated.CS/SonarLint.xml delete mode 100644 analyzers/its/sources/SkipGenerated.CS/SonarLint.xml delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/RuleWithBooleanParameter/SonarLint.xml delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/RuleWithStringParameter/SonarLint.xml delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/StringInsteadOfBoolean/SonarLint.xml delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/StringInsteadOfInt/SonarLint.xml diff --git a/analyzers/its/sources/AnalyzeGenerated.CS/SonarLint.xml b/analyzers/its/sources/AnalyzeGenerated.CS/SonarLint.xml deleted file mode 100644 index 15dbb76706c..00000000000 --- a/analyzers/its/sources/AnalyzeGenerated.CS/SonarLint.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - sonar.cs.ignoreHeaderComments - true - - - sonar.cs.analyzeGeneratedCode - true - - - sonar.cs.file.suffixes - .cs - - - - - S1067 - - - max - 1 - - - - - - - diff --git a/analyzers/its/sources/SkipGenerated.CS/SonarLint.xml b/analyzers/its/sources/SkipGenerated.CS/SonarLint.xml deleted file mode 100644 index b99df9c60f6..00000000000 --- a/analyzers/its/sources/SkipGenerated.CS/SonarLint.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - sonar.cs.ignoreHeaderComments - true - - - sonar.cs.analyzeGeneratedCode - false - - - sonar.cs.file.suffixes - .cs - - - - - S1067 - - - max - 1 - - - - - - - diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs index 5ed1026cd73..fb9a2ff026c 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs @@ -34,7 +34,7 @@ public class ParameterLoaderTest [TestMethod] [DataRow("path//aSonarLint.xml")] // different name [DataRow("path//SonarLint.xmla")] // different extension - public void SetParameterValues_WhenNoSonarLintIsGiven_DoesNotPopulateParameters(string filePath) + public void SetParameterValues_WithInvalidSonarLintPath_DoesNotPopulateParameters(string filePath) { // Arrange var compilation = CreateCompilationWithOption(filePath, SourceText.From(File.ReadAllText("ResourceTests\\SonarLintXml\\All_properties_cs\\SonarLint.xml"))); @@ -50,7 +50,7 @@ public void SetParameterValues_WhenNoSonarLintIsGiven_DoesNotPopulateParameters( [TestMethod] [DataRow("a/SonarLint.xml")] // unix path [DataRow("a\\SonarLint.xml")] - public void SetParameterValues_WhenGivenValidSonarLintFilePath_PopulatesProperties(string filePath) + public void SetParameterValues_WithValidSonarLintPath_PopulatesProperties(string filePath) { // Arrange var compilation = CreateCompilationWithOption(filePath, SourceText.From(File.ReadAllText("ResourceTests\\SonarLintXml\\All_properties_cs\\SonarLint.xml"))); @@ -64,7 +64,7 @@ public void SetParameterValues_WhenGivenValidSonarLintFilePath_PopulatesProperti } [TestMethod] - public void SetParameterValues_WhenGivenSonarLintFileHasIntParameterType_PopulatesProperties() + public void SetParameterValues_SonarLintFileWithIntParameterType_PopulatesProperties() { // Arrange var compilation = CreateCompilationWithOption("ResourceTests\\SonarLintXml\\All_properties_cs\\SonarLint.xml"); @@ -78,35 +78,39 @@ public void SetParameterValues_WhenGivenSonarLintFileHasIntParameterType_Populat } [TestMethod] - public void SetParameterValues_WhenGivenSonarLintFileHasStringParameterType_OnlyOneParameter_PopulatesProperty() + public void SetParameterValues_SonarLintFileWithStringParameterType_PopulatesProperty() { // Arrange - var compilation = CreateCompilationWithOption("ResourceTests\\SonarLintXml\\RuleWithStringParameter\\SonarLint.xml"); + var parameterValue = "1"; + var filePath = GenerateSonarLintXmlWithParametrizedRule("S2342", "flagsAttributeFormat", parameterValue); + var compilation = CreateCompilationWithOption(filePath); var analyzer = new EnumNameShouldFollowRegex(); // Cannot use mock because we use reflection to find properties. // Act ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); // Assert - analyzer.FlagsEnumNamePattern.Should().Be("1"); // value from XML file + analyzer.FlagsEnumNamePattern.Should().Be(parameterValue); // value from XML file } [TestMethod] - public void SetParameterValues_WhenGivenSonarLintFileHasBooleanParameterType_OnlyOneParameter_PopulatesProperty() + public void SetParameterValues_SonarLintFileWithBooleanParameterType_PopulatesProperty() { // Arrange - var compilation = CreateCompilationWithOption("ResourceTests\\SonarLintXml\\RuleWithBooleanParameter\\SonarLint.xml"); + var parameterValue = true; + var filePath = GenerateSonarLintXmlWithParametrizedRule("S1451", "isRegularExpression", parameterValue.ToString()); + var compilation = CreateCompilationWithOption(filePath); var analyzer = new CheckFileLicense(); // Cannot use mock because we use reflection to find properties. // Act ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); // Assert - analyzer.IsRegularExpression.Should().BeTrue(); // value from XML file + analyzer.IsRegularExpression.Should().Be(parameterValue); // value from XML file } [TestMethod] - public void SetParameterValues_WhenGivenValidSonarLintFileAndDoesNotContainAnalyzerParameters_DoesNotPopulateProperties() + public void SetParameterValues_SonarLintFileWithoutRuleParameters_DoesNotPopulateProperties() { // Arrange var compilation = CreateCompilationWithOption("ResourceTests\\SonarLintXml\\All_properties_cs\\SonarLint.xml"); @@ -177,10 +181,12 @@ public void SetParameterValues_WithMalformedXml_DoesNotPopulateProperties(string } [TestMethod] - public void SetParameterValues_WithWrongPropertyType_StringInsteadOfInt_DoesNotPopulateProperties() + public void SetParameterValues_SonarLintFileWithStringInsteadOfIntParameterType_PopulatesProperty() { // Arrange - var compilation = CreateCompilationWithOption("ResourceTests\\SonarLintXml\\StringInsteadOfInt\\SonarLint.xml"); + var parameterValue = "fooBar"; + var filePath = GenerateSonarLintXmlWithParametrizedRule("S1067", "max", parameterValue); + var compilation = CreateCompilationWithOption(filePath); var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act @@ -191,10 +197,12 @@ public void SetParameterValues_WithWrongPropertyType_StringInsteadOfInt_DoesNotP } [TestMethod] - public void SetParameterValues_WithWrongPropertyType_StringInsteadOfBoolean_DoesNotPopulateProperties() + public void SetParameterValues_SonarLintFileWithStringInsteadOfBooleanParameterType_PopulatesProperty() { // Arrange - var compilation = CreateCompilationWithOption("ResourceTests\\SonarLintXml\\StringInsteadOfBoolean\\SonarLint.xml"); + var parameterValue = "fooBar"; + var filePath = GenerateSonarLintXmlWithParametrizedRule("S1451", "isRegularExpression", parameterValue); + var compilation = CreateCompilationWithOption(filePath); var analyzer = new CheckFileLicense(); // Cannot use mock because we use reflection to find properties. // Act @@ -213,5 +221,25 @@ private static SonarCompilationReportingContext CreateCompilationWithOption(stri var compilationContext = new CompilationAnalysisContext(compilation, options, _ => { }, _ => true, default); return new(AnalysisScaffolding.CreateSonarAnalysisContext(), compilationContext); } + + private string GenerateSonarLintXmlWithParametrizedRule(string ruleId, string key, string value) + { + var ruleParameters = new List() + { + new SonarLintXmlRule() + { + Key = ruleId, + Parameters = new List() + { + new SonarLintXmlKeyValuePair() + { + Key = key, + Value = value + } + } + } + }; + return AnalysisScaffolding.CreateSonarLintXml(TestContext, rulesParameters: ruleParameters); + } } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/RuleWithBooleanParameter/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/RuleWithBooleanParameter/SonarLint.xml deleted file mode 100644 index 4170e60a90a..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/RuleWithBooleanParameter/SonarLint.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - sonar.cs.ignoreHeaderComments - true - - - sonar.cs.analyzeGeneratedCode - false - - - sonar.cs.file.suffixes - .cs - - - - - S1451 - - - isRegularExpression - true - - - - - - - diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/RuleWithStringParameter/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/RuleWithStringParameter/SonarLint.xml deleted file mode 100644 index d45ac7addb5..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/RuleWithStringParameter/SonarLint.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - sonar.cs.ignoreHeaderComments - true - - - sonar.cs.analyzeGeneratedCode - false - - - sonar.cs.file.suffixes - .cs - - - - - S2342 - - - flagsAttributeFormat - 1 - - - - - - - diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/StringInsteadOfBoolean/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/StringInsteadOfBoolean/SonarLint.xml deleted file mode 100644 index 913b8886a2d..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/StringInsteadOfBoolean/SonarLint.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - sonar.cs.ignoreHeaderComments - true - - - sonar.cs.analyzeGeneratedCode - false - - - sonar.cs.file.suffixes - .cs - - - - - S1451 - - - isRegularExpression - fooBar - - - - - - - diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/StringInsteadOfInt/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/StringInsteadOfInt/SonarLint.xml deleted file mode 100644 index 4596b44be4b..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/StringInsteadOfInt/SonarLint.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - sonar.cs.ignoreHeaderComments - true - - - sonar.cs.analyzeGeneratedCode - false - - - sonar.cs.file.suffixes - .cs - - - - - S1067 - - - max - fooBar - - - - - - - From 7f128129148486bd614efc2e7fb9dfcc9138d662 Mon Sep 17 00:00:00 2001 From: Cristian Ambrosini <114916336+cristian-ambrosini-sonarsource@users.noreply.github.com> Date: Wed, 15 Mar 2023 11:49:48 +0100 Subject: [PATCH 10/14] Fix SL feature branch ITs (#6927) --- .../SonarLintExclusions--net7.0-S1451.json | 17 +++++++++++ .../sources/AnalyzeGenerated.CS/SonarLint.xml | 30 +++++++++++++++++++ .../sources/SkipGenerated.CS/SonarLint.xml | 30 +++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 analyzers/its/expected/SonarLintExclusions/SonarLintExclusions--net7.0-S1451.json create mode 100644 analyzers/its/sources/AnalyzeGenerated.CS/SonarLint.xml create mode 100644 analyzers/its/sources/SkipGenerated.CS/SonarLint.xml diff --git a/analyzers/its/expected/SonarLintExclusions/SonarLintExclusions--net7.0-S1451.json b/analyzers/its/expected/SonarLintExclusions/SonarLintExclusions--net7.0-S1451.json new file mode 100644 index 00000000000..d4028681cc7 --- /dev/null +++ b/analyzers/its/expected/SonarLintExclusions/SonarLintExclusions--net7.0-S1451.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S1451", +"message": "Add or update the header of this file.", +"location": { +"uri": "sources\SonarLintExclusions\SonarLintExclusions\Included\Included.cs", +"region": { +"startLine": 1, +"startColumn": 1, +"endLine": 1, +"endColumn": 1 +} +} +} +] +} diff --git a/analyzers/its/sources/AnalyzeGenerated.CS/SonarLint.xml b/analyzers/its/sources/AnalyzeGenerated.CS/SonarLint.xml new file mode 100644 index 00000000000..637fabc2996 --- /dev/null +++ b/analyzers/its/sources/AnalyzeGenerated.CS/SonarLint.xml @@ -0,0 +1,30 @@ + + + + + sonar.cs.ignoreHeaderComments + true + + + sonar.cs.analyzeGeneratedCode + true + + + sonar.cs.file.suffixes + .cs + + + + + S1067 + + + max + 1 + + + + + + + \ No newline at end of file diff --git a/analyzers/its/sources/SkipGenerated.CS/SonarLint.xml b/analyzers/its/sources/SkipGenerated.CS/SonarLint.xml new file mode 100644 index 00000000000..b11b39f3e3c --- /dev/null +++ b/analyzers/its/sources/SkipGenerated.CS/SonarLint.xml @@ -0,0 +1,30 @@ + + + + + sonar.cs.ignoreHeaderComments + true + + + sonar.cs.analyzeGeneratedCode + false + + + sonar.cs.file.suffixes + .cs + + + + + S1067 + + + max + 1 + + + + + + + \ No newline at end of file From f0553a746a05ed054f6612b8dabab5b528340a62 Mon Sep 17 00:00:00 2001 From: Mary Georgiou <89914005+mary-georgiou-sonarsource@users.noreply.github.com> Date: Wed, 15 Mar 2023 16:10:49 +0100 Subject: [PATCH 11/14] Remove language property from SonarLintXmlReader (#6905) --- .../SonarAnalysisContextBase.cs | 10 ++--- .../Common/UnexpectedLanguageException.cs | 4 +- .../Helpers/SonarLintXmlReader.cs | 32 ++++++++++----- .../Rules/Utilities/UtilityAnalyzerBase.cs | 4 +- .../SonarAnalysisContextBaseTest.cs | 8 ++-- .../Common/UnexpectedLanguageExceptionTest.cs | 4 ++ .../Helpers/SonarLintXmlReaderTest.cs | 41 ++++++++++++++----- .../SonarLint.xml | 21 ++++++++++ 8 files changed, 88 insertions(+), 36 deletions(-) create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/PropertiesCSharpTrueVbnetFalse/SonarLint.xml diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs index 70fd96b25ee..e6f2ff25f68 100644 --- a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs @@ -30,13 +30,9 @@ public class SonarAnalysisContextBase protected static readonly ConditionalWeakTable> FileInclusionCache = new(); protected static readonly ConditionalWeakTable> UnchangedFilesCache = new(); protected static readonly SourceTextValueProvider ProjectConfigProvider = new(x => new ProjectConfigReader(x)); - protected static readonly SourceTextValueProvider SonarLintXmlProviderCS = new(x => new SonarLintXmlReader(x, LanguageNames.CSharp)); - protected static readonly SourceTextValueProvider SonarLintXmlProviderVB = new(x => new SonarLintXmlReader(x, LanguageNames.VisualBasic)); + protected static readonly SourceTextValueProvider SonarLintXmlProvider = new(x => new SonarLintXmlReader(x)); protected SonarAnalysisContextBase() { } - - protected static SourceTextValueProvider SonarLintXmlReader(string language) => - language == LanguageNames.CSharp ? SonarLintXmlProviderCS : SonarLintXmlProviderVB; } public abstract class SonarAnalysisContextBase : SonarAnalysisContextBase @@ -58,7 +54,7 @@ protected SonarAnalysisContextBase(SonarAnalysisContext analysisContext, TContex /// When set, generated trees are analyzed only when language-specific 'analyzeGeneratedCode' configuration property is also set. public bool ShouldAnalyzeTree(SyntaxTree tree, GeneratedCodeRecognizer generatedCodeRecognizer) => SonarLintFile() is var sonarLintReader - && (generatedCodeRecognizer is null || sonarLintReader.AnalyzeGeneratedCode || !tree.IsGenerated(generatedCodeRecognizer, Compilation)) + && (generatedCodeRecognizer is null || sonarLintReader.AnalyzeGeneratedCode(Compilation.Language) || !tree.IsGenerated(generatedCodeRecognizer, Compilation)) && (tree is null || (!IsUnchanged(tree) && ShouldAnalyzeFile(sonarLintReader, tree.FilePath))); /// @@ -88,7 +84,7 @@ public SonarLintXmlReader SonarLintFile() if (Options.SonarLintXml() is { } sonarLintXml) { return sonarLintXml.GetText() is { } sourceText - && AnalysisContext.TryGetValue(sourceText, SonarLintXmlReader(Compilation.Language), out var sonarLintXmlReader) + && AnalysisContext.TryGetValue(sourceText, SonarLintXmlProvider, out var sonarLintXmlReader) ? sonarLintXmlReader : throw new InvalidOperationException($"File '{Path.GetFileName(sonarLintXml.Path)}' has been added as an AdditionalFile but could not be read and parsed."); } diff --git a/analyzers/src/SonarAnalyzer.Common/Common/UnexpectedLanguageException.cs b/analyzers/src/SonarAnalyzer.Common/Common/UnexpectedLanguageException.cs index c71a99fca52..5585b04b6ad 100644 --- a/analyzers/src/SonarAnalyzer.Common/Common/UnexpectedLanguageException.cs +++ b/analyzers/src/SonarAnalyzer.Common/Common/UnexpectedLanguageException.cs @@ -22,6 +22,8 @@ namespace SonarAnalyzer.Common { public sealed class UnexpectedLanguageException : Exception { - public UnexpectedLanguageException(AnalyzerLanguage language) : base($"Unexpected language: {language}") { } + public UnexpectedLanguageException(AnalyzerLanguage language) : this(language.LanguageName) { } + + public UnexpectedLanguageException(string language) : base($"Unexpected language: {language}") { } } } diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs index 83fb3a13080..77cfddc9c1e 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs @@ -28,16 +28,29 @@ namespace SonarAnalyzer.Helpers; public class SonarLintXmlReader { - public static readonly SonarLintXmlReader Empty = new(null, LanguageNames.CSharp); + public static readonly SonarLintXmlReader Empty = new(null); private readonly SonarLintXml sonarLintXml; - private readonly string propertyLanguage; - private bool? ignoreHeaderComments; - public bool IgnoreHeaderComments => ignoreHeaderComments ??= ReadBoolean(ReadSettingsProperty($"sonar.{propertyLanguage}.ignoreHeaderComments")); - - private bool? analyzeGeneratedCode; - public bool AnalyzeGeneratedCode => analyzeGeneratedCode ??= ReadBoolean(ReadSettingsProperty($"sonar.{propertyLanguage}.analyzeGeneratedCode")); + private bool? ignoreHeaderCommentsCS; + private bool? ignoreHeaderCommentsVB; + public bool IgnoreHeaderComments(string language) => + language switch + { + LanguageNames.CSharp => ignoreHeaderCommentsCS ??= ReadBoolean(ReadSettingsProperty("sonar.cs.ignoreHeaderComments")), + LanguageNames.VisualBasic => ignoreHeaderCommentsVB ??= ReadBoolean(ReadSettingsProperty("sonar.vbnet.ignoreHeaderComments")), + _ => throw new UnexpectedLanguageException(language) + }; + + private bool? analyzeGeneratedCodeCS; + private bool? analyzeGeneratedCodeVB; + public bool AnalyzeGeneratedCode(string language) => + language switch + { + LanguageNames.CSharp => analyzeGeneratedCodeCS ??= ReadBoolean(ReadSettingsProperty("sonar.cs.analyzeGeneratedCode")), + LanguageNames.VisualBasic => analyzeGeneratedCodeVB ??= ReadBoolean(ReadSettingsProperty("sonar.vbnet.analyzeGeneratedCode")), + _ => throw new UnexpectedLanguageException(language) + }; private string[] exclusions; public string[] Exclusions => exclusions ??= ReadCommaSeparatedArray(ReadSettingsProperty("sonar.exclusions")); @@ -60,11 +73,8 @@ public class SonarLintXmlReader private List parametrizedRules; public List ParametrizedRules => parametrizedRules ??= ReadRuleParameters(); - public SonarLintXmlReader(SourceText sonarLintXml, string language = LanguageNames.CSharp) - { + public SonarLintXmlReader(SourceText sonarLintXml) => this.sonarLintXml = sonarLintXml == null ? SonarLintXml.Empty : ParseContent(sonarLintXml); - propertyLanguage = language == LanguageNames.CSharp ? "cs" : "vbnet"; - } private static SonarLintXml ParseContent(SourceText sonarLintXml) { diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index 6df9b93a53e..65627b91f2f 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -61,8 +61,8 @@ protected void ReadParameters(SonarCompilationStartAnalysisContext context) if (context.Options.SonarLintXml() != null && !string.IsNullOrEmpty(outPath)) { var sonarLintXml = context.SonarLintFile(); - IgnoreHeaderComments = sonarLintXml.IgnoreHeaderComments; - AnalyzeGeneratedCode = sonarLintXml.AnalyzeGeneratedCode; + IgnoreHeaderComments = sonarLintXml.IgnoreHeaderComments(context.Compilation.Language); + AnalyzeGeneratedCode = sonarLintXml.AnalyzeGeneratedCode(context.Compilation.Language); OutPath = Path.Combine(outPath, context.Compilation.Language == LanguageNames.CSharp ? "output-cs" : "output-vbnet"); IsAnalyzerEnabled = true; IsTestProject = context.IsTestProject(); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs index e0c98dd49aa..f527dd092a9 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs @@ -181,8 +181,8 @@ public void SonarLintFile_LoadsExpectedValues(string language) var options = AnalysisScaffolding.CreateOptions($"ResourceTests\\SonarLintXml\\All_properties_{language}\\SonarLint.xml"); var sut = CreateSut(compilation, options).SonarLintFile(); - sut.IgnoreHeaderComments.Should().BeTrue(); - sut.AnalyzeGeneratedCode.Should().BeFalse(); + sut.IgnoreHeaderComments(analyzerLanguage.LanguageName).Should().BeTrue(); + sut.AnalyzeGeneratedCode(analyzerLanguage.LanguageName).Should().BeFalse(); AssertArrayContent(sut.Exclusions, nameof(sut.Exclusions)); AssertArrayContent(sut.Inclusions, nameof(sut.Inclusions)); AssertArrayContent(sut.GlobalExclusions, nameof(sut.GlobalExclusions)); @@ -253,8 +253,8 @@ public void SonarLintFile_WhenFileIsMissing_ThrowException() private static void CheckSonarLintXmlDefaultValues(SonarLintXmlReader sut) { - sut.AnalyzeGeneratedCode.Should().BeFalse(); - sut.IgnoreHeaderComments.Should().BeFalse(); + sut.AnalyzeGeneratedCode(LanguageNames.CSharp).Should().BeFalse(); + sut.IgnoreHeaderComments(LanguageNames.CSharp).Should().BeFalse(); sut.Exclusions.Should().NotBeNull().And.HaveCount(0); sut.Inclusions.Should().NotBeNull().And.HaveCount(0); sut.GlobalExclusions.Should().NotBeNull().And.HaveCount(0); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Common/UnexpectedLanguageExceptionTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Common/UnexpectedLanguageExceptionTest.cs index 99c548a1acf..166d08a855c 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Common/UnexpectedLanguageExceptionTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Common/UnexpectedLanguageExceptionTest.cs @@ -25,6 +25,10 @@ namespace SonarAnalyzer.UnitTest.Common [TestClass] public class UnexpectedLanguageExceptionTest { + [TestMethod] + public void Message_String_Ctor() => + new UnexpectedLanguageException("F#").Message.Should().Be("Unexpected language: F#"); + [TestMethod] public void Message_CS() => new UnexpectedLanguageException(AnalyzerLanguage.CSharp).Message.Should().Be("Unexpected language: C#"); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs index 009508344c2..5caa7a84e9e 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs @@ -20,6 +20,7 @@ using System.IO; using Microsoft.CodeAnalysis.Text; +using SonarAnalyzer.Common; namespace SonarAnalyzer.UnitTest.Helpers; @@ -29,11 +30,11 @@ public class SonarLintXmlReaderTest [DataTestMethod] [DataRow(LanguageNames.CSharp, "cs")] [DataRow(LanguageNames.VisualBasic, "vbnet")] - public void SonarLintXmlReader_WhenAllValuesAreSet_ExpectedValues(string language, string propertyLanguage) + public void SonarLintXmlReader_WhenAllValuesAreSet_ExpectedValues(string language, string xmlLanguageName) { - var sut = CreateSonarLintXmlReader($"ResourceTests\\SonarLintXml\\All_Properties_{propertyLanguage}\\SonarLint.xml", language); - sut.IgnoreHeaderComments.Should().BeTrue(); - sut.AnalyzeGeneratedCode.Should().BeFalse(); + var sut = CreateSonarLintXmlReader($"ResourceTests\\SonarLintXml\\All_Properties_{xmlLanguageName}\\SonarLint.xml"); + sut.IgnoreHeaderComments(language).Should().BeTrue(); + sut.AnalyzeGeneratedCode(language).Should().BeFalse(); AssertArrayContent(sut.Exclusions, nameof(sut.Exclusions)); AssertArrayContent(sut.Inclusions, nameof(sut.Inclusions)); AssertArrayContent(sut.GlobalExclusions, nameof(sut.GlobalExclusions)); @@ -60,8 +61,8 @@ static void AssertArrayContent(string[] array, string folder) public void SonarLintXmlReader_PartiallyMissingProperties_ExpectedAndDefaultValues() { var sut = CreateSonarLintXmlReader("ResourceTests\\SonarLintXml\\Partially_missing_properties\\SonarLint.xml"); - sut.IgnoreHeaderComments.Should().BeFalse(); - sut.AnalyzeGeneratedCode.Should().BeTrue(); + sut.IgnoreHeaderComments(LanguageNames.CSharp).Should().BeFalse(); + sut.AnalyzeGeneratedCode(LanguageNames.CSharp).Should().BeTrue(); AssertArrayContent(sut.Exclusions, nameof(sut.Exclusions)); AssertArrayContent(sut.Inclusions, nameof(sut.Inclusions)); sut.GlobalExclusions.Should().NotBeNull().And.HaveCount(0); @@ -71,12 +72,22 @@ public void SonarLintXmlReader_PartiallyMissingProperties_ExpectedAndDefaultValu sut.ParametrizedRules.Should().NotBeNull().And.HaveCount(0); } + [TestMethod] + public void SonarLintXmlReader_PropertiesCSharpTrueVBNetFalse_ExpectedValues() + { + var sut = CreateSonarLintXmlReader("ResourceTests\\SonarLintXml\\PropertiesCSharpTrueVbnetFalse\\SonarLint.xml"); + sut.IgnoreHeaderComments(LanguageNames.CSharp).Should().BeTrue(); + sut.IgnoreHeaderComments(LanguageNames.VisualBasic).Should().BeFalse(); + sut.AnalyzeGeneratedCode(LanguageNames.CSharp).Should().BeTrue(); + sut.AnalyzeGeneratedCode(LanguageNames.VisualBasic).Should().BeFalse(); + } + [DataTestMethod] [DataRow("")] [DataRow("this is not an xml")] [DataRow(@"")] public void SonarLintXmlReader_WithMalformedXml_DefaultBehaviour(string sonarLintXmlContent) => - CheckSonarLintXmlReaderDefaultValues(new SonarLintXmlReader(SourceText.From(sonarLintXmlContent), LanguageNames.CSharp)); + CheckSonarLintXmlReaderDefaultValues(new SonarLintXmlReader(SourceText.From(sonarLintXmlContent))); [TestMethod] public void SonarLintXmlReader_MissingProperties_DefaultBehaviour() => @@ -90,10 +101,18 @@ public void SonarLintXmlReader_PartiallyMissingProperties_ExpectedAndDefaultValu public void SonarLintXmlReader_CheckEmpty_DefaultBehaviour() => CheckSonarLintXmlReaderDefaultValues(SonarLintXmlReader.Empty); + [TestMethod] + public void SonarLintXmlReader_LanguageDoesNotExist_Throws() + { + var sut = CreateSonarLintXmlReader($"ResourceTests\\SonarLintXml\\All_Properties_cs\\SonarLint.xml"); + sut.Invoking(x => x.IgnoreHeaderComments(LanguageNames.FSharp)).Should().Throw().WithMessage("Unexpected language: F#"); + sut.Invoking(x => x.AnalyzeGeneratedCode(LanguageNames.FSharp)).Should().Throw().WithMessage("Unexpected language: F#"); + } + private static void CheckSonarLintXmlReaderDefaultValues(SonarLintXmlReader sut) { - sut.AnalyzeGeneratedCode.Should().BeFalse(); - sut.IgnoreHeaderComments.Should().BeFalse(); + sut.AnalyzeGeneratedCode(LanguageNames.CSharp).Should().BeFalse(); + sut.IgnoreHeaderComments(LanguageNames.CSharp).Should().BeFalse(); sut.Exclusions.Should().NotBeNull().And.HaveCount(0); sut.Inclusions.Should().NotBeNull().And.HaveCount(0); sut.GlobalExclusions.Should().NotBeNull().And.HaveCount(0); @@ -110,6 +129,6 @@ private static void AssertArrayContent(string[] array, string folder) array[1].Should().BeEquivalentTo($"Fake/{folder}/Second*/**/*"); } - private static SonarLintXmlReader CreateSonarLintXmlReader(string relativePath, string language = LanguageNames.CSharp) => - new(SourceText.From(File.ReadAllText(relativePath)), language); + private static SonarLintXmlReader CreateSonarLintXmlReader(string relativePath) => + new(SourceText.From(File.ReadAllText(relativePath))); } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/PropertiesCSharpTrueVbnetFalse/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/PropertiesCSharpTrueVbnetFalse/SonarLint.xml new file mode 100644 index 00000000000..39ddbc9a84e --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/PropertiesCSharpTrueVbnetFalse/SonarLint.xml @@ -0,0 +1,21 @@ + + + + + sonar.cs.ignoreHeaderComments + true + + + sonar.vbnet.ignoreHeaderComments + false + + + sonar.cs.analyzeGeneratedCode + true + + + sonar.vbnet.analyzeGeneratedCode + false + + + \ No newline at end of file From 62efe30fb421c75abf053ea9a63842b23df8a5ff Mon Sep 17 00:00:00 2001 From: Mary Georgiou <89914005+mary-georgiou-sonarsource@users.noreply.github.com> Date: Wed, 15 Mar 2023 16:53:23 +0100 Subject: [PATCH 12/14] SonarAnalysisContextBase refactoring (#6915) --- .../SonarAnalysisContextBase.cs | 18 ++++++++------- .../ParametrizedDiagnosticAnalyzer.cs | 2 +- .../Helpers/SonarLintXmlReader.cs | 4 ++-- .../Rules/Utilities/UtilityAnalyzerBase.cs | 2 +- .../SonarAnalysisContextBaseTest.cs | 16 +++++++------- .../Helpers/ParameterLoaderTest.cs | 22 +++++++++---------- 6 files changed, 33 insertions(+), 31 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs index e6f2ff25f68..90889c8ffd0 100644 --- a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs @@ -53,9 +53,9 @@ protected SonarAnalysisContextBase(SonarAnalysisContext analysisContext, TContex /// Tree to decide on. Can be null for Symbol-based and Compilation-based scenarios. And we want to analyze those too. /// When set, generated trees are analyzed only when language-specific 'analyzeGeneratedCode' configuration property is also set. public bool ShouldAnalyzeTree(SyntaxTree tree, GeneratedCodeRecognizer generatedCodeRecognizer) => - SonarLintFile() is var sonarLintReader - && (generatedCodeRecognizer is null || sonarLintReader.AnalyzeGeneratedCode(Compilation.Language) || !tree.IsGenerated(generatedCodeRecognizer, Compilation)) - && (tree is null || (!IsUnchanged(tree) && ShouldAnalyzeFile(sonarLintReader, tree.FilePath))); + SonarLintXml() is var sonarLintXml + && (generatedCodeRecognizer is null || sonarLintXml.AnalyzeGeneratedCode(Compilation.Language) || !tree.IsGenerated(generatedCodeRecognizer, Compilation)) + && (tree is null || (!IsUnchanged(tree) && !IsExcluded(sonarLintXml, tree.FilePath))); /// /// Reads configuration from SonarProjectConfig.xml file and caches the result for scope of this analysis. @@ -79,7 +79,7 @@ public ProjectConfigReader ProjectConfiguration() /// /// Reads the properties from the SonarLint.xml file and caches the result for the scope of this analysis. /// - public SonarLintXmlReader SonarLintFile() + public SonarLintXmlReader SonarLintXml() { if (Options.SonarLintXml() is { } sonarLintXml) { @@ -121,10 +121,12 @@ public bool HasMatchingScope(DiagnosticDescriptor descriptor) descriptor.CustomTags.Contains(tag); } - private bool ShouldAnalyzeFile(SonarLintXmlReader sonarLintXml, string filePath) => - ProjectConfiguration().ProjectType != ProjectType.Unknown // Not SonarLint context, NuGet or Scanner <= 5.0 - || (FileInclusionCache.GetValue(Compilation, _ => new()) is var cache - && cache.GetOrAdd(filePath, _ => IsFileIncluded(sonarLintXml, filePath))); + private bool IsExcluded(SonarLintXmlReader sonarLintXml, string filePath) => + // If ProjectType is not 'Unknown' it means we are in S4NET context and all files are analyzed. + // If ProjectType is 'Unknown' then we are in SonarLint or NuGet context and we need to check if the file has been excluded from analysis through SonarLint.xml. + ProjectConfiguration().ProjectType == ProjectType.Unknown + && FileInclusionCache.GetValue(Compilation, _ => new()) is var cache + && !cache.GetOrAdd(filePath, _ => IsFileIncluded(sonarLintXml, filePath)); private ImmutableHashSet CreateUnchangedFilesHashSet() => ImmutableHashSet.Create(StringComparer.OrdinalIgnoreCase, ProjectConfiguration().AnalysisConfig?.UnchangedFiles() ?? Array.Empty()); diff --git a/analyzers/src/SonarAnalyzer.Common/DiagnosticAnalyzer/ParametrizedDiagnosticAnalyzer.cs b/analyzers/src/SonarAnalyzer.Common/DiagnosticAnalyzer/ParametrizedDiagnosticAnalyzer.cs index cb3d06010dd..46f7ea5533a 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.SonarLintFile()); + ParameterLoader.SetParameterValues(this, c.SonarLintXml()); parameterContext.ExecutePostponedActions(c); }); } diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs index 77cfddc9c1e..3f9af7d7d45 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs @@ -100,8 +100,8 @@ private static SonarLintXml ParseContent(SourceText sonarLintXml) private string ReadSettingsProperty(string property) => sonarLintXml is { Settings: { } settings } - ? settings.Where(x => x.Key.Equals(property)).Select(x => x.Value).FirstOrDefault() - : string.Empty; + ? settings.Where(x => x.Key.Equals(property)).Select(x => x.Value).FirstOrDefault() + : null; private static string[] ReadCommaSeparatedArray(string str) => string.IsNullOrEmpty(str) ? Array.Empty() : str.Split(','); diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index 65627b91f2f..c2508a645c2 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -60,7 +60,7 @@ protected void ReadParameters(SonarCompilationStartAnalysisContext context) } if (context.Options.SonarLintXml() != null && !string.IsNullOrEmpty(outPath)) { - var sonarLintXml = context.SonarLintFile(); + var sonarLintXml = context.SonarLintXml(); IgnoreHeaderComments = sonarLintXml.IgnoreHeaderComments(context.Compilation.Language); AnalyzeGeneratedCode = sonarLintXml.AnalyzeGeneratedCode(context.Compilation.Language); OutPath = Path.Combine(outPath, context.Compilation.Language == LanguageNames.CSharp ? "output-cs" : "output-vbnet"); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs index f527dd092a9..bc44a11477e 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs @@ -179,7 +179,7 @@ public void SonarLintFile_LoadsExpectedValues(string language) var analyzerLanguage = language == "cs" ? AnalyzerLanguage.CSharp : AnalyzerLanguage.VisualBasic; var (compilation, _) = CreateDummyCompilation(analyzerLanguage, "ExtraEmptyFile"); var options = AnalysisScaffolding.CreateOptions($"ResourceTests\\SonarLintXml\\All_properties_{language}\\SonarLint.xml"); - var sut = CreateSut(compilation, options).SonarLintFile(); + var sut = CreateSut(compilation, options).SonarLintXml(); sut.IgnoreHeaderComments(analyzerLanguage.LanguageName).Should().BeTrue(); sut.AnalyzeGeneratedCode(analyzerLanguage.LanguageName).Should().BeFalse(); @@ -204,8 +204,8 @@ public void SonarLintFile_UsesCachedValue() var options = AnalysisScaffolding.CreateOptions("ResourceTests\\SonarLintXml\\All_properties_cs\\SonarLint.xml"); var firstSut = CreateSut(options); var secondSut = CreateSut(options); - var firstFile = firstSut.SonarLintFile(); - var secondFile = secondSut.SonarLintFile(); + var firstFile = firstSut.SonarLintXml(); + var secondFile = secondSut.SonarLintXml(); secondFile.Should().BeSameAs(firstFile); } @@ -215,8 +215,8 @@ public void SonarLintFile_WhenFileChanges_RebuildsCache() { var firstOptions = AnalysisScaffolding.CreateOptions("ResourceTests\\SonarLintXml\\All_properties_cs\\SonarLint.xml"); var secondOptions = AnalysisScaffolding.CreateOptions("ResourceTests\\SonarLintXml\\All_properties_vbnet\\SonarLint.xml"); - var firstFile = CreateSut(firstOptions).SonarLintFile(); - var secondFile = CreateSut(secondOptions).SonarLintFile(); + var firstFile = CreateSut(firstOptions).SonarLintXml(); + var secondFile = CreateSut(secondOptions).SonarLintXml(); secondFile.Should().NotBeSameAs(firstFile); } @@ -229,14 +229,14 @@ public void SonarLintFile_WhenFileChanges_RebuildsCache() [DataRow("path//SonarLint.xmla")] // different extension public void SonarLintFile_WhenAdditionalFileNotPresent_ReturnsDefaultValues(string folder) { - var sut = CreateSut(AnalysisScaffolding.CreateOptions(folder)).SonarLintFile(); + var sut = CreateSut(AnalysisScaffolding.CreateOptions(folder)).SonarLintXml(); CheckSonarLintXmlDefaultValues(sut); } [TestMethod] public void SonarLintFile_WhenInvalidXml_ReturnsDefaultValues() { - var sut = CreateSut(AnalysisScaffolding.CreateOptions("ResourceTests\\SonarLintXml\\Invalid_Xml\\SonarLint.xml")).SonarLintFile(); + var sut = CreateSut(AnalysisScaffolding.CreateOptions("ResourceTests\\SonarLintXml\\Invalid_Xml\\SonarLint.xml")).SonarLintXml(); CheckSonarLintXmlDefaultValues(sut); } @@ -245,7 +245,7 @@ public void SonarLintFile_WhenFileIsMissing_ThrowException() { var sut = CreateSut(AnalysisScaffolding.CreateOptions("ThisPathDoesNotExist\\SonarLint.xml")); - sut.Invoking(x => x.SonarLintFile()) + sut.Invoking(x => x.SonarLintXml()) .Should() .Throw() .WithMessage("File 'SonarLint.xml' has been added as an AdditionalFile but could not be read and parsed."); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs index fb9a2ff026c..5d599cd21c9 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs @@ -41,7 +41,7 @@ public void SetParameterValues_WithInvalidSonarLintPath_DoesNotPopulateParameter var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.Maximum.Should().Be(3); // Default value @@ -57,7 +57,7 @@ public void SetParameterValues_WithValidSonarLintPath_PopulatesProperties(string var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.Maximum.Should().Be(1); // Value from the xml file @@ -71,7 +71,7 @@ public void SetParameterValues_SonarLintFileWithIntParameterType_PopulatesProper var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.Maximum.Should().Be(1); // Value from the xml file @@ -87,7 +87,7 @@ public void SetParameterValues_SonarLintFileWithStringParameterType_PopulatesPro var analyzer = new EnumNameShouldFollowRegex(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.FlagsEnumNamePattern.Should().Be(parameterValue); // value from XML file @@ -103,7 +103,7 @@ public void SetParameterValues_SonarLintFileWithBooleanParameterType_PopulatesPr var analyzer = new CheckFileLicense(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.IsRegularExpression.Should().Be(parameterValue); // value from XML file @@ -117,7 +117,7 @@ public void SetParameterValues_SonarLintFileWithoutRuleParameters_DoesNotPopulat var analyzer = new LineLength(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.Maximum.Should().Be(200); // Default value @@ -149,7 +149,7 @@ public void SetParameterValues_CalledTwiceAfterChangeInConfigFile_UpdatesPropert var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); analyzer.Maximum.Should().Be(maxValue); // Modify the in-memory additional file @@ -159,7 +159,7 @@ public void SetParameterValues_CalledTwiceAfterChangeInConfigFile_UpdatesPropert var modifiedFilePath = TestHelper.WriteFile(TestContext, "SonarLint.xml", modifiedSonarLintXml); compilation = CreateCompilationWithOption(modifiedFilePath); - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); analyzer.Maximum.Should().Be(maxValue); } @@ -174,7 +174,7 @@ public void SetParameterValues_WithMalformedXml_DoesNotPopulateProperties(string var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.Maximum.Should().Be(3); // Default value @@ -190,7 +190,7 @@ public void SetParameterValues_SonarLintFileWithStringInsteadOfIntParameterType_ var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.Maximum.Should().Be(3); // Default value @@ -206,7 +206,7 @@ public void SetParameterValues_SonarLintFileWithStringInsteadOfBooleanParameterT var analyzer = new CheckFileLicense(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.IsRegularExpression.Should().BeFalse(); // Default value From a60ea62ae62df3a6854ced9e33849436a4f5b7c0 Mon Sep 17 00:00:00 2001 From: Cristian Ambrosini <114916336+cristian-ambrosini-sonarsource@users.noreply.github.com> Date: Thu, 16 Mar 2023 08:32:35 +0100 Subject: [PATCH 13/14] WildcardPatternMatcher improvement (#6919) --- .../SonarAnalysisContextBase.cs | 4 +- .../Helpers/WildcardPatternMatcher.cs | 134 +++++++----------- .../Helpers/WildcardPatternMatcherTest.cs | 65 +++------ 3 files changed, 75 insertions(+), 128 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs index 90889c8ffd0..cc9dedd3618 100644 --- a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs @@ -142,8 +142,8 @@ public bool HasMatchingScope(DiagnosticDescriptor descriptor) && !IsExcluded(globalExclusions, filePath); private static bool IsIncluded(string[] inclusions, string filePath) => - inclusions is { Length: 0 } || inclusions.Any(x => WildcardPatternMatcher.IsMatch(x, filePath)); + inclusions is { Length: 0 } || inclusions.Any(x => WildcardPatternMatcher.IsMatch(x, filePath, true)); private static bool IsExcluded(string[] exclusions, string filePath) => - exclusions.Any(x => WildcardPatternMatcher.IsMatch(x, filePath)); + exclusions.Any(x => WildcardPatternMatcher.IsMatch(x, filePath, false)); } diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/WildcardPatternMatcher.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/WildcardPatternMatcher.cs index b72a702d8e9..1054d7d867b 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/WildcardPatternMatcher.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/WildcardPatternMatcher.cs @@ -27,108 +27,76 @@ namespace SonarAnalyzer.Helpers; internal static class WildcardPatternMatcher { - public static bool IsMatch(string pattern, string input) => + private static readonly ConcurrentDictionary Cache = new(); + + public static bool IsMatch(string pattern, string input, bool timeoutFallbackResult) => !(string.IsNullOrWhiteSpace(pattern) || string.IsNullOrWhiteSpace(input)) - && WildcardPattern.Create(pattern).Match(input); + && Cache.GetOrAdd(pattern, _ => new Regex(ToRegex(pattern), RegexOptions.None, RegexConstants.DefaultTimeout)) is var regex + && IsMatch(regex, input, timeoutFallbackResult); - /// - /// Copied from https://github.com/SonarSource/sonar-plugin-api/blob/a9bd7ff48f0f77811ed909070030678c443c975a/sonar-plugin-api/src/main/java/org/sonar/api/utils/WildcardPattern.java. - /// - private sealed class WildcardPattern + private static bool IsMatch(Regex regex, string value, bool timeoutFallbackResult) { - private const string SpecialChars = "()[]^$.{}+|"; - private static readonly ConcurrentDictionary Cache = new(); - private readonly Regex pattern; - - private WildcardPattern(string pattern, string directorySeparator) => - this.pattern = new Regex(ToRegexp(pattern, directorySeparator), RegexOptions.Compiled, RegexConstants.DefaultTimeout); - - public bool Match(string value) + try { - value = value.TrimStart('/'); - value = value.TrimEnd('/'); - try - { - return pattern.IsMatch(value); - } - catch (RegexMatchTimeoutException) - { - return false; - } + return regex.IsMatch(value.Trim('/')); } - - public static WildcardPattern Create(string pattern) => - Create(pattern, Path.DirectorySeparatorChar.ToString()); - - private static WildcardPattern Create(string pattern, string directorySeparator) => - Cache.GetOrAdd(pattern + directorySeparator, _ => new WildcardPattern(pattern, directorySeparator)); - - private static string ToRegexp(string wildcardPattern, string directorySeparator) + catch (RegexMatchTimeoutException) { - var escapedDirectorySeparator = '\\' + directorySeparator; - var sb = new StringBuilder(wildcardPattern.Length); - - sb.Append('^'); + return timeoutFallbackResult; + } + } - var i = wildcardPattern.StartsWith("/") || wildcardPattern.StartsWith("\\") ? 1 : 0; - while (i < wildcardPattern.Length) + /// + /// Copied from https://github.com/SonarSource/sonar-plugin-api/blob/a9bd7ff48f0f77811ed909070030678c443c975a/sonar-plugin-api/src/main/java/org/sonar/api/utils/WildcardPattern.java. + /// + private static string ToRegex(string wildcardPattern) + { + var escapedDirectorySeparator = Regex.Escape(Path.DirectorySeparatorChar.ToString()); + var sb = new StringBuilder("^", wildcardPattern.Length); + var i = IsSlash(wildcardPattern[0]) ? 1 : 0; + while (i < wildcardPattern.Length) + { + var ch = wildcardPattern[i]; + if (ch == '*') { - var ch = wildcardPattern[i]; - - if (SpecialChars.IndexOf(ch) != -1) + if (i + 1 < wildcardPattern.Length && wildcardPattern[i + 1] == '*') { - // Escape regexp-specific characters - sb.Append('\\').Append(ch); - } - else if (ch == '*') - { - if (i + 1 < wildcardPattern.Length && wildcardPattern[i + 1] == '*') + // Double asterisk - Zero or more directories + if (i + 2 < wildcardPattern.Length && IsSlash(wildcardPattern[i + 2])) { - // Double asterisk - // Zero or more directories - if (i + 2 < wildcardPattern.Length && IsSlash(wildcardPattern[i + 2])) - { - sb.Append("(?:.*").Append(escapedDirectorySeparator).Append("|)"); - i += 2; - } - else - { - sb.Append(".*"); - i += 1; - } + sb.Append($"(.*{escapedDirectorySeparator}|)"); + i += 2; } else { - // Single asterisk - // Zero or more characters excluding directory separator - sb.Append("[^").Append(escapedDirectorySeparator).Append("]*?"); + sb.Append(".*"); + i += 1; } } - else if (ch == '?') - { - // Any single character excluding directory separator - sb.Append("[^").Append(escapedDirectorySeparator).Append("]"); - } - else if (IsSlash(ch)) - { - // Directory separator - sb.Append(escapedDirectorySeparator); - } else { - // Single character - sb.Append(ch); + // Single asterisk - Zero or more characters excluding directory separator + sb.Append($"[^{escapedDirectorySeparator}]*?"); } - - i++; } - - sb.Append('$'); - - return sb.ToString(); + else if (ch == '?') + { + // Any single character excluding directory separator + sb.Append($"[^{escapedDirectorySeparator}]"); + } + else if (IsSlash(ch)) + { + sb.Append(escapedDirectorySeparator); + } + else + { + sb.Append(Regex.Escape(ch.ToString())); + } + i++; } - - private static bool IsSlash(char ch) => - ch == '/' || ch == '\\'; + return sb.Append('$').ToString(); } + + private static bool IsSlash(char ch) => + ch == '/' || ch == '\\'; } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/WildcardPatternMatcherTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/WildcardPatternMatcherTest.cs index f8b02ce4aa8..79273d566d2 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/WildcardPatternMatcherTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/WildcardPatternMatcherTest.cs @@ -29,87 +29,66 @@ public class WildcardPatternMatcherTest /// Based on https://github.com/SonarSource/sonar-plugin-api/blob/master/plugin-api/src/test/java/org/sonar/api/utils/WildcardPatternTest.java. /// [DataTestMethod] - [DataRow("Foo", "Foo", true)] [DataRow("foo", "FOO", false)] [DataRow("Foo", "Foot", false)] [DataRow("Foo", "Bar", false)] - - [DataRow("org/T?st.java", "org/Test.java", true)] - [DataRow("org/T?st.java", "org/Tost.java", true)] - [DataRow("org/T?st.java", "org/Teeest.java", false)] - - [DataRow("org/*.java", "org/Foo.java", true)] - [DataRow("org/*.java", "org/Bar.java", true)] - - [DataRow("org/**", "org/Foo.java", true)] + [DataRow("org/T?st.cs", "org/Test.cs", true)] + [DataRow("org/T?st.cs", "org/Tost.cs", true)] + [DataRow("org/T?st.cs", "org/Teeest.cs", false)] + [DataRow("org/*.cs", "org/Foo.cs", true)] + [DataRow("org/*.cs", "org/Bar.cs", true)] + [DataRow("org/**", "org/Foo.cs", true)] [DataRow("org/**", "org/foo/bar.jsp", true)] - - [DataRow("org/**/Test.java", "org/Test.java", true)] - [DataRow("org/**/Test.java", "org/foo/Test.java", true)] - [DataRow("org/**/Test.java", "org/foo/bar/Test.java", true)] - - [DataRow("org/**/*.java", "org/Foo.java", true)] - [DataRow("org/**/*.java", "org/foo/Bar.java", true)] - [DataRow("org/**/*.java", "org/foo/bar/Baz.java", true)] - - [DataRow("o?/**/*.java", "org/test.java", false)] - [DataRow("o?/**/*.java", "o/test.java", false)] - [DataRow("o?/**/*.java", "og/test.java", true)] - [DataRow("o?/**/*.java", "og/foo/bar/test.java", true)] - [DataRow("o?/**/*.java", "og/foo/bar/test.jav", false)] - + [DataRow("org/**/Test.cs", "org/Test.cs", true)] + [DataRow("org/**/Test.cs", "org/foo/Test.cs", true)] + [DataRow("org/**/Test.cs", "org/foo/bar/Test.cs", true)] + [DataRow("org/**/*.cs", "org/Foo.cs", true)] + [DataRow("org/**/*.cs", "org/foo/Bar.cs", true)] + [DataRow("org/**/*.cs", "org/foo/bar/Baz.cs", true)] + [DataRow("o?/**/*.cs", "org/test.cs", false)] + [DataRow("o?/**/*.cs", "o/test.cs", false)] + [DataRow("o?/**/*.cs", "og/test.cs", true)] + [DataRow("o?/**/*.cs", "og/foo/bar/test.cs", true)] + [DataRow("o?/**/*.cs", "og/foo/bar/test.c", false)] [DataRow("org/sonar/**", "org/sonar/commons/Foo", true)] - [DataRow("org/sonar/**", "org/sonar/Foo.java", true)] - + [DataRow("org/sonar/**", "org/sonar/Foo.cs", true)] [DataRow("xxx/org/sonar/**", "org/sonar/Foo", false)] - [DataRow("org/sonar/**/**", "org/sonar/commons/Foo", true)] - [DataRow("org/sonar/**/**", "org/sonar/commons/sub/Foo.java", true)] - + [DataRow("org/sonar/**/**", "org/sonar/commons/sub/Foo.cs", true)] [DataRow("org/sonar/**/Foo", "org/sonar/commons/sub/Foo", true)] [DataRow("org/sonar/**/Foo", "org/sonar/Foo", true)] - [DataRow("*/foo/*", "org/foo/Bar", true)] [DataRow("*/foo/*", "foo/Bar", false)] [DataRow("*/foo/*", "foo", false)] [DataRow("*/foo/*", "org/foo/bar/Hello", false)] - [DataRow("hell?", "hell", false)] [DataRow("hell?", "hello", true)] [DataRow("hell?", "helloworld", false)] - [DataRow("**/Reader", "java/io/Reader", true)] [DataRow("**/Reader", "org/sonar/channel/CodeReader", false)] - [DataRow("**", "java/io/Reader", true)] - [DataRow("**/app/**", "com/app/Utils", true)] [DataRow("**/app/**", "com/application/MyService", false)] - [DataRow("**/*$*", "foo/bar", false)] [DataRow("**/*$*", "foo/bar$baz", true)] [DataRow("a+", "aa", false)] [DataRow("a+", "a+", true)] [DataRow("[ab]", "a", false)] [DataRow("[ab]", "[ab]", true)] - [DataRow("\\n", "\n", false)] [DataRow("foo\\bar", "foo/bar", true)] - [DataRow("/foo", "foo", true)] [DataRow("\\foo", "foo", true)] - [DataRow("foo\\bar", "foo\\bar", true)] [DataRow("foo/bar", "foo\\bar", true)] [DataRow("foo\\bar/baz", "foo\\bar\\baz", true)] - public void IsMatch_MatchesPatternsAsExpected(string pattern, string input, bool expectedResult) { // The test cases are copied from the plugin-api and the directory separators need replacing as Roslyn will not give us the paths with '/'. input = input.Replace("/", Path.DirectorySeparatorChar.ToString()); - WildcardPatternMatcher.IsMatch(pattern, input).Should().Be(expectedResult); + WildcardPatternMatcher.IsMatch(pattern, input, false).Should().Be(expectedResult); } [DataTestMethod] @@ -118,12 +97,12 @@ public void IsMatch_MatchesPatternsAsExpected(string pattern, string input, bool [DataRow("/")] [DataRow("\\")] public void IsMatch_InvalidPattern_ReturnsFalse(string pattern) => - WildcardPatternMatcher.IsMatch(pattern, "foo").Should().BeFalse(); + WildcardPatternMatcher.IsMatch(pattern, "foo", false).Should().BeFalse(); [DataTestMethod] [DataRow(null, "foo")] [DataRow("foo", null)] public void IsMatch_InputParametersArenull_DoesNotThrow(string pattern, string input) => - WildcardPatternMatcher.IsMatch(pattern, input).Should().BeFalse(); + WildcardPatternMatcher.IsMatch(pattern, input, false).Should().BeFalse(); } } From a1797220dc443e848fc1a8b73f2bf1e0417df104 Mon Sep 17 00:00:00 2001 From: Cristian Ambrosini <114916336+cristian-ambrosini-sonarsource@users.noreply.github.com> Date: Thu, 16 Mar 2023 13:47:08 +0100 Subject: [PATCH 14/14] SonarLintXmlReader revert to previous ReadContent implementation (#6939) --- .../src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs index 3f9af7d7d45..ec69d9c96a3 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs @@ -81,11 +81,8 @@ private static SonarLintXml ParseContent(SourceText sonarLintXml) try { var serializer = new XmlSerializer(typeof(SonarLintXml)); - var byteArray = Encoding.UTF8.GetBytes(sonarLintXml.ToString()); - var stream = new MemoryStream(byteArray); - using var sr = new StreamReader(stream, Encoding.UTF8, false); - using var reader = XmlReader.Create(sr); - return (SonarLintXml)serializer.Deserialize(reader); + using var sr = new StringReader(sonarLintXml.ToString()); + return (SonarLintXml)serializer.Deserialize(sr); } catch {