diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs index 70fd96b25ee..e6f2ff25f68 100644 --- a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs @@ -30,13 +30,9 @@ public class SonarAnalysisContextBase protected static readonly ConditionalWeakTable> FileInclusionCache = new(); protected static readonly ConditionalWeakTable> UnchangedFilesCache = new(); protected static readonly SourceTextValueProvider ProjectConfigProvider = new(x => new ProjectConfigReader(x)); - protected static readonly SourceTextValueProvider SonarLintXmlProviderCS = new(x => new SonarLintXmlReader(x, LanguageNames.CSharp)); - protected static readonly SourceTextValueProvider SonarLintXmlProviderVB = new(x => new SonarLintXmlReader(x, LanguageNames.VisualBasic)); + protected static readonly SourceTextValueProvider SonarLintXmlProvider = new(x => new SonarLintXmlReader(x)); protected SonarAnalysisContextBase() { } - - protected static SourceTextValueProvider SonarLintXmlReader(string language) => - language == LanguageNames.CSharp ? SonarLintXmlProviderCS : SonarLintXmlProviderVB; } public abstract class SonarAnalysisContextBase : SonarAnalysisContextBase @@ -58,7 +54,7 @@ protected SonarAnalysisContextBase(SonarAnalysisContext analysisContext, TContex /// When set, generated trees are analyzed only when language-specific 'analyzeGeneratedCode' configuration property is also set. public bool ShouldAnalyzeTree(SyntaxTree tree, GeneratedCodeRecognizer generatedCodeRecognizer) => SonarLintFile() is var sonarLintReader - && (generatedCodeRecognizer is null || sonarLintReader.AnalyzeGeneratedCode || !tree.IsGenerated(generatedCodeRecognizer, Compilation)) + && (generatedCodeRecognizer is null || sonarLintReader.AnalyzeGeneratedCode(Compilation.Language) || !tree.IsGenerated(generatedCodeRecognizer, Compilation)) && (tree is null || (!IsUnchanged(tree) && ShouldAnalyzeFile(sonarLintReader, tree.FilePath))); /// @@ -88,7 +84,7 @@ public SonarLintXmlReader SonarLintFile() if (Options.SonarLintXml() is { } sonarLintXml) { return sonarLintXml.GetText() is { } sourceText - && AnalysisContext.TryGetValue(sourceText, SonarLintXmlReader(Compilation.Language), out var sonarLintXmlReader) + && AnalysisContext.TryGetValue(sourceText, SonarLintXmlProvider, out var sonarLintXmlReader) ? sonarLintXmlReader : throw new InvalidOperationException($"File '{Path.GetFileName(sonarLintXml.Path)}' has been added as an AdditionalFile but could not be read and parsed."); } diff --git a/analyzers/src/SonarAnalyzer.Common/Common/UnexpectedLanguageException.cs b/analyzers/src/SonarAnalyzer.Common/Common/UnexpectedLanguageException.cs index c71a99fca52..5585b04b6ad 100644 --- a/analyzers/src/SonarAnalyzer.Common/Common/UnexpectedLanguageException.cs +++ b/analyzers/src/SonarAnalyzer.Common/Common/UnexpectedLanguageException.cs @@ -22,6 +22,8 @@ namespace SonarAnalyzer.Common { public sealed class UnexpectedLanguageException : Exception { - public UnexpectedLanguageException(AnalyzerLanguage language) : base($"Unexpected language: {language}") { } + public UnexpectedLanguageException(AnalyzerLanguage language) : this(language.LanguageName) { } + + public UnexpectedLanguageException(string language) : base($"Unexpected language: {language}") { } } } diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs index 83fb3a13080..77cfddc9c1e 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs @@ -28,16 +28,29 @@ namespace SonarAnalyzer.Helpers; public class SonarLintXmlReader { - public static readonly SonarLintXmlReader Empty = new(null, LanguageNames.CSharp); + public static readonly SonarLintXmlReader Empty = new(null); private readonly SonarLintXml sonarLintXml; - private readonly string propertyLanguage; - private bool? ignoreHeaderComments; - public bool IgnoreHeaderComments => ignoreHeaderComments ??= ReadBoolean(ReadSettingsProperty($"sonar.{propertyLanguage}.ignoreHeaderComments")); - - private bool? analyzeGeneratedCode; - public bool AnalyzeGeneratedCode => analyzeGeneratedCode ??= ReadBoolean(ReadSettingsProperty($"sonar.{propertyLanguage}.analyzeGeneratedCode")); + private bool? ignoreHeaderCommentsCS; + private bool? ignoreHeaderCommentsVB; + public bool IgnoreHeaderComments(string language) => + language switch + { + LanguageNames.CSharp => ignoreHeaderCommentsCS ??= ReadBoolean(ReadSettingsProperty("sonar.cs.ignoreHeaderComments")), + LanguageNames.VisualBasic => ignoreHeaderCommentsVB ??= ReadBoolean(ReadSettingsProperty("sonar.vbnet.ignoreHeaderComments")), + _ => throw new UnexpectedLanguageException(language) + }; + + private bool? analyzeGeneratedCodeCS; + private bool? analyzeGeneratedCodeVB; + public bool AnalyzeGeneratedCode(string language) => + language switch + { + LanguageNames.CSharp => analyzeGeneratedCodeCS ??= ReadBoolean(ReadSettingsProperty("sonar.cs.analyzeGeneratedCode")), + LanguageNames.VisualBasic => analyzeGeneratedCodeVB ??= ReadBoolean(ReadSettingsProperty("sonar.vbnet.analyzeGeneratedCode")), + _ => throw new UnexpectedLanguageException(language) + }; private string[] exclusions; public string[] Exclusions => exclusions ??= ReadCommaSeparatedArray(ReadSettingsProperty("sonar.exclusions")); @@ -60,11 +73,8 @@ public class SonarLintXmlReader private List parametrizedRules; public List ParametrizedRules => parametrizedRules ??= ReadRuleParameters(); - public SonarLintXmlReader(SourceText sonarLintXml, string language = LanguageNames.CSharp) - { + public SonarLintXmlReader(SourceText sonarLintXml) => this.sonarLintXml = sonarLintXml == null ? SonarLintXml.Empty : ParseContent(sonarLintXml); - propertyLanguage = language == LanguageNames.CSharp ? "cs" : "vbnet"; - } private static SonarLintXml ParseContent(SourceText sonarLintXml) { diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index 6df9b93a53e..65627b91f2f 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -61,8 +61,8 @@ protected void ReadParameters(SonarCompilationStartAnalysisContext context) if (context.Options.SonarLintXml() != null && !string.IsNullOrEmpty(outPath)) { var sonarLintXml = context.SonarLintFile(); - IgnoreHeaderComments = sonarLintXml.IgnoreHeaderComments; - AnalyzeGeneratedCode = sonarLintXml.AnalyzeGeneratedCode; + IgnoreHeaderComments = sonarLintXml.IgnoreHeaderComments(context.Compilation.Language); + AnalyzeGeneratedCode = sonarLintXml.AnalyzeGeneratedCode(context.Compilation.Language); OutPath = Path.Combine(outPath, context.Compilation.Language == LanguageNames.CSharp ? "output-cs" : "output-vbnet"); IsAnalyzerEnabled = true; IsTestProject = context.IsTestProject(); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs index e0c98dd49aa..f527dd092a9 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs @@ -181,8 +181,8 @@ public void SonarLintFile_LoadsExpectedValues(string language) var options = AnalysisScaffolding.CreateOptions($"ResourceTests\\SonarLintXml\\All_properties_{language}\\SonarLint.xml"); var sut = CreateSut(compilation, options).SonarLintFile(); - sut.IgnoreHeaderComments.Should().BeTrue(); - sut.AnalyzeGeneratedCode.Should().BeFalse(); + sut.IgnoreHeaderComments(analyzerLanguage.LanguageName).Should().BeTrue(); + sut.AnalyzeGeneratedCode(analyzerLanguage.LanguageName).Should().BeFalse(); AssertArrayContent(sut.Exclusions, nameof(sut.Exclusions)); AssertArrayContent(sut.Inclusions, nameof(sut.Inclusions)); AssertArrayContent(sut.GlobalExclusions, nameof(sut.GlobalExclusions)); @@ -253,8 +253,8 @@ public void SonarLintFile_WhenFileIsMissing_ThrowException() private static void CheckSonarLintXmlDefaultValues(SonarLintXmlReader sut) { - sut.AnalyzeGeneratedCode.Should().BeFalse(); - sut.IgnoreHeaderComments.Should().BeFalse(); + sut.AnalyzeGeneratedCode(LanguageNames.CSharp).Should().BeFalse(); + sut.IgnoreHeaderComments(LanguageNames.CSharp).Should().BeFalse(); sut.Exclusions.Should().NotBeNull().And.HaveCount(0); sut.Inclusions.Should().NotBeNull().And.HaveCount(0); sut.GlobalExclusions.Should().NotBeNull().And.HaveCount(0); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Common/UnexpectedLanguageExceptionTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Common/UnexpectedLanguageExceptionTest.cs index 99c548a1acf..166d08a855c 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Common/UnexpectedLanguageExceptionTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Common/UnexpectedLanguageExceptionTest.cs @@ -25,6 +25,10 @@ namespace SonarAnalyzer.UnitTest.Common [TestClass] public class UnexpectedLanguageExceptionTest { + [TestMethod] + public void Message_String_Ctor() => + new UnexpectedLanguageException("F#").Message.Should().Be("Unexpected language: F#"); + [TestMethod] public void Message_CS() => new UnexpectedLanguageException(AnalyzerLanguage.CSharp).Message.Should().Be("Unexpected language: C#"); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs index 009508344c2..5caa7a84e9e 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/SonarLintXmlReaderTest.cs @@ -20,6 +20,7 @@ using System.IO; using Microsoft.CodeAnalysis.Text; +using SonarAnalyzer.Common; namespace SonarAnalyzer.UnitTest.Helpers; @@ -29,11 +30,11 @@ public class SonarLintXmlReaderTest [DataTestMethod] [DataRow(LanguageNames.CSharp, "cs")] [DataRow(LanguageNames.VisualBasic, "vbnet")] - public void SonarLintXmlReader_WhenAllValuesAreSet_ExpectedValues(string language, string propertyLanguage) + public void SonarLintXmlReader_WhenAllValuesAreSet_ExpectedValues(string language, string xmlLanguageName) { - var sut = CreateSonarLintXmlReader($"ResourceTests\\SonarLintXml\\All_Properties_{propertyLanguage}\\SonarLint.xml", language); - sut.IgnoreHeaderComments.Should().BeTrue(); - sut.AnalyzeGeneratedCode.Should().BeFalse(); + var sut = CreateSonarLintXmlReader($"ResourceTests\\SonarLintXml\\All_Properties_{xmlLanguageName}\\SonarLint.xml"); + sut.IgnoreHeaderComments(language).Should().BeTrue(); + sut.AnalyzeGeneratedCode(language).Should().BeFalse(); AssertArrayContent(sut.Exclusions, nameof(sut.Exclusions)); AssertArrayContent(sut.Inclusions, nameof(sut.Inclusions)); AssertArrayContent(sut.GlobalExclusions, nameof(sut.GlobalExclusions)); @@ -60,8 +61,8 @@ static void AssertArrayContent(string[] array, string folder) public void SonarLintXmlReader_PartiallyMissingProperties_ExpectedAndDefaultValues() { var sut = CreateSonarLintXmlReader("ResourceTests\\SonarLintXml\\Partially_missing_properties\\SonarLint.xml"); - sut.IgnoreHeaderComments.Should().BeFalse(); - sut.AnalyzeGeneratedCode.Should().BeTrue(); + sut.IgnoreHeaderComments(LanguageNames.CSharp).Should().BeFalse(); + sut.AnalyzeGeneratedCode(LanguageNames.CSharp).Should().BeTrue(); AssertArrayContent(sut.Exclusions, nameof(sut.Exclusions)); AssertArrayContent(sut.Inclusions, nameof(sut.Inclusions)); sut.GlobalExclusions.Should().NotBeNull().And.HaveCount(0); @@ -71,12 +72,22 @@ public void SonarLintXmlReader_PartiallyMissingProperties_ExpectedAndDefaultValu sut.ParametrizedRules.Should().NotBeNull().And.HaveCount(0); } + [TestMethod] + public void SonarLintXmlReader_PropertiesCSharpTrueVBNetFalse_ExpectedValues() + { + var sut = CreateSonarLintXmlReader("ResourceTests\\SonarLintXml\\PropertiesCSharpTrueVbnetFalse\\SonarLint.xml"); + sut.IgnoreHeaderComments(LanguageNames.CSharp).Should().BeTrue(); + sut.IgnoreHeaderComments(LanguageNames.VisualBasic).Should().BeFalse(); + sut.AnalyzeGeneratedCode(LanguageNames.CSharp).Should().BeTrue(); + sut.AnalyzeGeneratedCode(LanguageNames.VisualBasic).Should().BeFalse(); + } + [DataTestMethod] [DataRow("")] [DataRow("this is not an xml")] [DataRow(@"")] public void SonarLintXmlReader_WithMalformedXml_DefaultBehaviour(string sonarLintXmlContent) => - CheckSonarLintXmlReaderDefaultValues(new SonarLintXmlReader(SourceText.From(sonarLintXmlContent), LanguageNames.CSharp)); + CheckSonarLintXmlReaderDefaultValues(new SonarLintXmlReader(SourceText.From(sonarLintXmlContent))); [TestMethod] public void SonarLintXmlReader_MissingProperties_DefaultBehaviour() => @@ -90,10 +101,18 @@ public void SonarLintXmlReader_PartiallyMissingProperties_ExpectedAndDefaultValu public void SonarLintXmlReader_CheckEmpty_DefaultBehaviour() => CheckSonarLintXmlReaderDefaultValues(SonarLintXmlReader.Empty); + [TestMethod] + public void SonarLintXmlReader_LanguageDoesNotExist_Throws() + { + var sut = CreateSonarLintXmlReader($"ResourceTests\\SonarLintXml\\All_Properties_cs\\SonarLint.xml"); + sut.Invoking(x => x.IgnoreHeaderComments(LanguageNames.FSharp)).Should().Throw().WithMessage("Unexpected language: F#"); + sut.Invoking(x => x.AnalyzeGeneratedCode(LanguageNames.FSharp)).Should().Throw().WithMessage("Unexpected language: F#"); + } + private static void CheckSonarLintXmlReaderDefaultValues(SonarLintXmlReader sut) { - sut.AnalyzeGeneratedCode.Should().BeFalse(); - sut.IgnoreHeaderComments.Should().BeFalse(); + sut.AnalyzeGeneratedCode(LanguageNames.CSharp).Should().BeFalse(); + sut.IgnoreHeaderComments(LanguageNames.CSharp).Should().BeFalse(); sut.Exclusions.Should().NotBeNull().And.HaveCount(0); sut.Inclusions.Should().NotBeNull().And.HaveCount(0); sut.GlobalExclusions.Should().NotBeNull().And.HaveCount(0); @@ -110,6 +129,6 @@ private static void AssertArrayContent(string[] array, string folder) array[1].Should().BeEquivalentTo($"Fake/{folder}/Second*/**/*"); } - private static SonarLintXmlReader CreateSonarLintXmlReader(string relativePath, string language = LanguageNames.CSharp) => - new(SourceText.From(File.ReadAllText(relativePath)), language); + private static SonarLintXmlReader CreateSonarLintXmlReader(string relativePath) => + new(SourceText.From(File.ReadAllText(relativePath))); } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/PropertiesCSharpTrueVbnetFalse/SonarLint.xml b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/PropertiesCSharpTrueVbnetFalse/SonarLint.xml new file mode 100644 index 00000000000..39ddbc9a84e --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/ResourceTests/SonarLintXml/PropertiesCSharpTrueVbnetFalse/SonarLint.xml @@ -0,0 +1,21 @@ + + + + + sonar.cs.ignoreHeaderComments + true + + + sonar.vbnet.ignoreHeaderComments + false + + + sonar.cs.analyzeGeneratedCode + true + + + sonar.vbnet.analyzeGeneratedCode + false + + + \ No newline at end of file