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-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/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/AnalyzeGenerated.CS/SonarLint.xml b/analyzers/its/sources/AnalyzeGenerated.CS/SonarLint.xml
index 15dbb76706c..637fabc2996 100644
--- a/analyzers/its/sources/AnalyzeGenerated.CS/SonarLint.xml
+++ b/analyzers/its/sources/AnalyzeGenerated.CS/SonarLint.xml
@@ -1,4 +1,4 @@
-
+
@@ -27,4 +27,4 @@
-
+
\ No newline at end of file
diff --git a/analyzers/its/sources/SkipGenerated.CS/SonarLint.xml b/analyzers/its/sources/SkipGenerated.CS/SonarLint.xml
index b99df9c60f6..b11b39f3e3c 100644
--- a/analyzers/its/sources/SkipGenerated.CS/SonarLint.xml
+++ b/analyzers/its/sources/SkipGenerated.CS/SonarLint.xml
@@ -1,4 +1,4 @@
-
+
@@ -27,4 +27,4 @@
-
+
\ No newline at end of file
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
diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs
index e18e7365ae7..cc9dedd3618 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,18 +27,12 @@ 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));
- private static readonly Lazy> ShouldAnalyzeGeneratedCS = new(() => CreateAnalyzeGeneratedProvider(LanguageNames.CSharp));
- private static readonly Lazy> ShouldAnalyzeGeneratedVB = new(() => CreateAnalyzeGeneratedProvider(LanguageNames.VisualBasic));
+ protected static readonly SourceTextValueProvider SonarLintXmlProvider = new(x => new SonarLintXmlReader(x));
protected SonarAnalysisContextBase() { }
-
- 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
@@ -58,8 +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) =>
- (generatedCodeRecognizer is null || ShouldAnalyzeGenerated() || !tree.IsGenerated(generatedCodeRecognizer, Compilation))
- && (tree is null || !IsUnchanged(tree));
+ 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.
@@ -80,6 +76,24 @@ public ProjectConfigReader ProjectConfiguration()
}
}
+ ///
+ /// Reads the properties from the SonarLint.xml file and caches the result for the scope of this analysis.
+ ///
+ public SonarLintXmlReader SonarLintXml()
+ {
+ if (Options.SonarLintXml() is { } sonarLintXml)
+ {
+ return sonarLintXml.GetText() is { } sourceText
+ && 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.");
+ }
+ else
+ {
+ return Helpers.SonarLintXmlReader.Empty;
+ }
+ }
+
public bool IsTestProject()
{
var projectType = ProjectConfiguration().ProjectType;
@@ -107,11 +121,29 @@ public bool HasMatchingScope(DiagnosticDescriptor descriptor)
descriptor.CustomTags.Contains(tag);
}
+ 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());
- private bool ShouldAnalyzeGenerated() =>
- Options.SonarLintXml() is { } sonarLintXml
- && AnalysisContext.TryGetValue(sonarLintXml.GetText(), ShouldAnalyzeGeneratedProvider(Compilation.Language), out var shouldAnalyzeGenerated)
- && shouldAnalyzeGenerated;
+ 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, true));
+
+ private static bool IsExcluded(string[] exclusions, string filePath) =>
+ exclusions.Any(x => WildcardPatternMatcher.IsMatch(x, filePath, false));
}
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/DiagnosticAnalyzer/ParametrizedDiagnosticAnalyzer.cs b/analyzers/src/SonarAnalyzer.Common/DiagnosticAnalyzer/ParametrizedDiagnosticAnalyzer.cs
index c1ae6279f85..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.Options);
+ ParameterLoader.SetParameterValues(this, c.SonarLintXml());
parameterContext.ExecutePostponedActions(c);
});
}
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/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/PropertiesHelper.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/PropertiesHelper.cs
deleted file mode 100644
index 55844a6ba58..00000000000
--- a/analyzers/src/SonarAnalyzer.Common/Helpers/PropertiesHelper.cs
+++ /dev/null
@@ -1,60 +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");
-
- 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";
- 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/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..ec69d9c96a3
--- /dev/null
+++ b/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs
@@ -0,0 +1,108 @@
+/*
+ * 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);
+
+ private readonly SonarLintXml sonarLintXml;
+
+ 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"));
+
+ private string[] inclusions;
+ public string[] Inclusions => inclusions ??= ReadCommaSeparatedArray(ReadSettingsProperty("sonar.inclusions"));
+
+ private string[] globalExclusions;
+ public string[] GlobalExclusions => globalExclusions ??= ReadCommaSeparatedArray(ReadSettingsProperty("sonar.global.exclusions"));
+
+ private string[] testExclusions;
+ public string[] TestExclusions => testExclusions ??= ReadCommaSeparatedArray(ReadSettingsProperty("sonar.test.exclusions"));
+
+ private string[] testInclusions;
+ public string[] TestInclusions => testInclusions ??= ReadCommaSeparatedArray(ReadSettingsProperty("sonar.test.inclusions"));
+
+ private string[] globalTestExclusions;
+ public string[] GlobalTestExclusions => globalTestExclusions ??= ReadCommaSeparatedArray(ReadSettingsProperty("sonar.global.test.exclusions"));
+
+ private List parametrizedRules;
+ public List ParametrizedRules => parametrizedRules ??= ReadRuleParameters();
+
+ public SonarLintXmlReader(SourceText sonarLintXml) =>
+ this.sonarLintXml = sonarLintXml == null ? SonarLintXml.Empty : ParseContent(sonarLintXml);
+
+ private static SonarLintXml ParseContent(SourceText sonarLintXml)
+ {
+ try
+ {
+ var serializer = new XmlSerializer(typeof(SonarLintXml));
+ using var sr = new StringReader(sonarLintXml.ToString());
+ return (SonarLintXml)serializer.Deserialize(sr);
+ }
+ catch
+ {
+ return SonarLintXml.Empty;
+ }
+ }
+
+ 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()
+ : null;
+
+ 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/src/SonarAnalyzer.Common/Helpers/WildcardPatternMatcher.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/WildcardPatternMatcher.cs
new file mode 100644
index 00000000000..1054d7d867b
--- /dev/null
+++ b/analyzers/src/SonarAnalyzer.Common/Helpers/WildcardPatternMatcher.cs
@@ -0,0 +1,102 @@
+/*
+ * 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
+{
+ private static readonly ConcurrentDictionary Cache = new();
+
+ public static bool IsMatch(string pattern, string input, bool timeoutFallbackResult) =>
+ !(string.IsNullOrWhiteSpace(pattern) || string.IsNullOrWhiteSpace(input))
+ && Cache.GetOrAdd(pattern, _ => new Regex(ToRegex(pattern), RegexOptions.None, RegexConstants.DefaultTimeout)) is var regex
+ && IsMatch(regex, input, timeoutFallbackResult);
+
+ private static bool IsMatch(Regex regex, string value, bool timeoutFallbackResult)
+ {
+ try
+ {
+ return regex.IsMatch(value.Trim('/'));
+ }
+ catch (RegexMatchTimeoutException)
+ {
+ return 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 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 == '*')
+ {
+ 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($"(.*{escapedDirectorySeparator}|)");
+ i += 2;
+ }
+ else
+ {
+ sb.Append(".*");
+ i += 1;
+ }
+ }
+ else
+ {
+ // Single asterisk - Zero or more characters excluding directory separator
+ sb.Append($"[^{escapedDirectorySeparator}]*?");
+ }
+ }
+ 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++;
+ }
+ return sb.Append('$').ToString();
+ }
+
+ private static bool IsSlash(char ch) =>
+ ch == '/' || ch == '\\';
+}
diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs
index 9ccc48ce48f..c2508a645c2 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 = PropertiesHelper.ReadIgnoreHeaderCommentsProperty(settings, language);
- AnalyzeGeneratedCode = PropertiesHelper.ReadAnalyzeGeneratedCodeProperty(settings, language);
- OutPath = Path.Combine(outPath, language == LanguageNames.CSharp ? "output-cs" : "output-vbnet");
+ 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");
IsAnalyzerEnabled = true;
IsTestProject = context.IsTestProject();
}
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/AnalysisContext/SonarAnalysisContextBaseTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs
index d402e7b82a4..bc44a11477e 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,98 @@ 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).SonarLintXml();
+
+ 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));
+ 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.SonarLintXml();
+ var secondFile = secondSut.SonarLintXml();
+
+ 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).SonarLintXml();
+ var secondFile = CreateSut(secondOptions).SonarLintXml();
+
+ secondFile.Should().NotBeSameAs(firstFile);
+ }
+
+ [DataTestMethod]
+ [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)).SonarLintXml();
+ CheckSonarLintXmlDefaultValues(sut);
+ }
+
+ [TestMethod]
+ public void SonarLintFile_WhenInvalidXml_ReturnsDefaultValues()
+ {
+ var sut = CreateSut(AnalysisScaffolding.CreateOptions("ResourceTests\\SonarLintXml\\Invalid_Xml\\SonarLint.xml")).SonarLintXml();
+ CheckSonarLintXmlDefaultValues(sut);
+ }
+
+ [TestMethod]
+ public void SonarLintFile_WhenFileIsMissing_ThrowException()
+ {
+ var sut = CreateSut(AnalysisScaffolding.CreateOptions("ThisPathDoesNotExist\\SonarLint.xml"));
+
+ sut.Invoking(x => x.SonarLintXml())
+ .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(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);
+ 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/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/ParameterLoaderTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs
index 646ceb2fb34..5d599cd21c9 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)
+ public void SetParameterValues_WithInvalidSonarLintPath_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\\SonarLintXml\\All_properties_cs\\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.SonarLintXml());
// Assert
analyzer.Maximum.Should().Be(3); // Default value
@@ -46,144 +50,117 @@ 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 options = AnalysisScaffolding.CreateOptions(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
- ParameterLoader.SetParameterValues(analyzer, options);
+ ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml());
// Assert
analyzer.Maximum.Should().Be(1); // Value from the xml file
}
[TestMethod]
- public void SetParameterValues_WhenGivenSonarLintFileHasIntParameterType_PopulatesProperties()
+ public void SetParameterValues_SonarLintFileWithIntParameterType_PopulatesProperties()
{
// Arrange
- var options = AnalysisScaffolding.CreateOptions("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
- ParameterLoader.SetParameterValues(analyzer, options);
+ ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml());
// Assert
analyzer.Maximum.Should().Be(1); // Value from the xml file
}
[TestMethod]
- public void SetParameterValues_WhenGivenSonarLintFileHasStringParameterType_OnlyOneParameter_PopulatesProperty()
+ public void SetParameterValues_SonarLintFileWithStringParameterType_PopulatesProperty()
{
// Arrange
- var options = AnalysisScaffolding.CreateOptions("ResourceTests\\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, options);
+ ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml());
// 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 options = AnalysisScaffolding.CreateOptions("ResourceTests\\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, options);
+ ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml());
// 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 options = AnalysisScaffolding.CreateOptions("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
- ParameterLoader.SetParameterValues(analyzer, options);
+ ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml());
// 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.SonarLintXml());
+ 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.SonarLintXml());
+ analyzer.Maximum.Should().Be(maxValue);
}
[TestMethod]
@@ -193,42 +170,76 @@ 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.SonarLintXml());
// Assert
analyzer.Maximum.Should().Be(3); // Default value
}
[TestMethod]
- public void SetParameterValues_WithWrongPropertyType_StringInsteadOfInt_DoesNotPopulateProperties()
+ public void SetParameterValues_SonarLintFileWithStringInsteadOfIntParameterType_PopulatesProperty()
{
// Arrange
- var options = AnalysisScaffolding.CreateOptions("ResourceTests\\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
- ParameterLoader.SetParameterValues(analyzer, options);
+ ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml());
// Assert
analyzer.Maximum.Should().Be(3); // Default value
}
[TestMethod]
- public void SetParameterValues_WithWrongPropertyType_StringInsteadOfBoolean_DoesNotPopulateProperties()
+ public void SetParameterValues_SonarLintFileWithStringInsteadOfBooleanParameterType_PopulatesProperty()
{
// Arrange
- var options = AnalysisScaffolding.CreateOptions("ResourceTests\\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
- ParameterLoader.SetParameterValues(analyzer, options);
+ ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml());
// 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);
+ }
+
+ 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/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/Helpers/SonarLintXmlReaderTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs
new file mode 100644
index 00000000000..5caa7a84e9e
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.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.IO;
+using Microsoft.CodeAnalysis.Text;
+using SonarAnalyzer.Common;
+
+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 xmlLanguageName)
+ {
+ 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));
+ AssertArrayContent(sut.TestExclusions, nameof(sut.TestExclusions));
+ AssertArrayContent(sut.TestInclusions, nameof(sut.TestInclusions));
+ AssertArrayContent(sut.GlobalTestExclusions, nameof(sut.GlobalTestExclusions));
+
+ 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})?$");
+ 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);
+ 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(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);
+ 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);
+ }
+
+ [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)));
+
+ [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);
+
+ [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(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);
+ 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)
+ {
+ array.Should().HaveCount(2);
+ array[0].Should().BeEquivalentTo($"Fake/{folder}/**/*");
+ array[1].Should().BeEquivalentTo($"Fake/{folder}/Second*/**/*");
+ }
+
+ private static SonarLintXmlReader CreateSonarLintXmlReader(string relativePath) =>
+ new(SourceText.From(File.ReadAllText(relativePath)));
+}
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlTest.cs
new file mode 100644
index 00000000000..47c834dbe53
--- /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_cs\\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("S2225");
+ rules[0].Parameters.Should().BeEmpty();
+
+ 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[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)
+ {
+ pair.Key.Should().BeEquivalentTo(expectedKey);
+ pair.Value.Should().BeEquivalentTo(expectedValue);
+ }
+}
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/WildcardPatternMatcherTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/WildcardPatternMatcherTest.cs
new file mode 100644
index 00000000000..79273d566d2
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/WildcardPatternMatcherTest.cs
@@ -0,0 +1,108 @@
+/*
+ * 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.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.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.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.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, false).Should().Be(expectedResult);
+ }
+
+ [DataTestMethod]
+ [DataRow("")]
+ [DataRow(" ")]
+ [DataRow("/")]
+ [DataRow("\\")]
+ public void IsMatch_InvalidPattern_ReturnsFalse(string pattern) =>
+ 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, false).Should().BeFalse();
+ }
+}
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/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
-
-
-
-
-
-
-
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/RuleWithBooleanParameter/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/RuleWithBooleanParameter/SonarLint.xml
deleted file mode 100644
index 4170e60a90a..00000000000
--- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/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/RuleWithStringParameter/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/RuleWithStringParameter/SonarLint.xml
deleted file mode 100644
index d45ac7addb5..00000000000
--- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/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/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
new file mode 100644
index 00000000000..54a1ed9e464
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_cs/SonarLint.xml
@@ -0,0 +1,77 @@
+
+
+
+
+ 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
+
+
+ S2342
+
+
+ format
+ ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$
+
+
+ flagsAttributeFormat
+ ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?s$
+
+
+
+
+ S2346
+
+
+ S1067
+
+
+ max
+ 1
+
+
+
+
+
+
+
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..270c5c95dd8
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/All_properties_vbnet/SonarLint.xml
@@ -0,0 +1,81 @@
+
+
+
+
+ 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
+
+
+ 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
+
+
+ S3776
+
+
+ threshold
+ 15
+
+
+ propertyThreshold
+ 3
+
+
+
+
+
+
+
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*/**/*
+
+
+
+
+
+
+
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
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/StringInsteadOfBoolean/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/StringInsteadOfBoolean/SonarLint.xml
deleted file mode 100644
index 913b8886a2d..00000000000
--- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/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/StringInsteadOfInt/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/StringInsteadOfInt/SonarLint.xml
deleted file mode 100644
index 4596b44be4b..00000000000
--- a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/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
-
-
-
-
-
-
-
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 d15e6b096af..6c6b8ef5e42 100644
--- a/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/AnalysisScaffolding.cs
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestFramework/AnalysisScaffolding.cs
@@ -19,6 +19,8 @@
*/
using System.IO;
+using System.Runtime.CompilerServices;
+using System.Xml.Linq;
using Microsoft.CodeAnalysis.Text;
using Moq;
using SonarAnalyzer.AnalysisContext;
@@ -80,6 +82,72 @@ 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,
+ bool ignoreHeaderComments = false,
+ string[] exclusions = null,
+ string[] inclusions = null,
+ string[] globalExclusions = null,
+ string[] testExclusions = null,
+ string[] testInclusions = null,
+ string[] globalTestExclusions = null,
+ List rulesParameters = null) =>
+ 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,
+ string[] testExclusions = null,
+ string[] testInclusions = null,
+ string[] globalTestExclusions = null,
+ List rulesParameters = 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.{(language == LanguageNames.CSharp ? "cs" : "vbnet")}.ignoreHeaderComments", ignoreHeaderComments.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))),
+ 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) =>
+ 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());
+
private static string CreateSonarProjectConfig(TestContext context, string element, string value, bool isScannerRun, string analysisConfigPath = null)
{
var sonarProjectConfigPath = TestHelper.TestPath(context, "SonarProjectConfig.xml");