diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs index e6f2ff25f68..90889c8ffd0 100644 --- a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs @@ -53,9 +53,9 @@ protected SonarAnalysisContextBase(SonarAnalysisContext analysisContext, TContex /// Tree to decide on. Can be null for Symbol-based and Compilation-based scenarios. And we want to analyze those too. /// When set, generated trees are analyzed only when language-specific 'analyzeGeneratedCode' configuration property is also set. public bool ShouldAnalyzeTree(SyntaxTree tree, GeneratedCodeRecognizer generatedCodeRecognizer) => - SonarLintFile() is var sonarLintReader - && (generatedCodeRecognizer is null || sonarLintReader.AnalyzeGeneratedCode(Compilation.Language) || !tree.IsGenerated(generatedCodeRecognizer, Compilation)) - && (tree is null || (!IsUnchanged(tree) && ShouldAnalyzeFile(sonarLintReader, tree.FilePath))); + SonarLintXml() is var sonarLintXml + && (generatedCodeRecognizer is null || sonarLintXml.AnalyzeGeneratedCode(Compilation.Language) || !tree.IsGenerated(generatedCodeRecognizer, Compilation)) + && (tree is null || (!IsUnchanged(tree) && !IsExcluded(sonarLintXml, tree.FilePath))); /// /// Reads configuration from SonarProjectConfig.xml file and caches the result for scope of this analysis. @@ -79,7 +79,7 @@ public ProjectConfigReader ProjectConfiguration() /// /// Reads the properties from the SonarLint.xml file and caches the result for the scope of this analysis. /// - public SonarLintXmlReader SonarLintFile() + public SonarLintXmlReader SonarLintXml() { if (Options.SonarLintXml() is { } sonarLintXml) { @@ -121,10 +121,12 @@ public bool HasMatchingScope(DiagnosticDescriptor descriptor) descriptor.CustomTags.Contains(tag); } - private bool ShouldAnalyzeFile(SonarLintXmlReader sonarLintXml, string filePath) => - ProjectConfiguration().ProjectType != ProjectType.Unknown // Not SonarLint context, NuGet or Scanner <= 5.0 - || (FileInclusionCache.GetValue(Compilation, _ => new()) is var cache - && cache.GetOrAdd(filePath, _ => IsFileIncluded(sonarLintXml, filePath))); + private bool IsExcluded(SonarLintXmlReader sonarLintXml, string filePath) => + // If ProjectType is not 'Unknown' it means we are in S4NET context and all files are analyzed. + // If ProjectType is 'Unknown' then we are in SonarLint or NuGet context and we need to check if the file has been excluded from analysis through SonarLint.xml. + ProjectConfiguration().ProjectType == ProjectType.Unknown + && FileInclusionCache.GetValue(Compilation, _ => new()) is var cache + && !cache.GetOrAdd(filePath, _ => IsFileIncluded(sonarLintXml, filePath)); private ImmutableHashSet CreateUnchangedFilesHashSet() => ImmutableHashSet.Create(StringComparer.OrdinalIgnoreCase, ProjectConfiguration().AnalysisConfig?.UnchangedFiles() ?? Array.Empty()); diff --git a/analyzers/src/SonarAnalyzer.Common/DiagnosticAnalyzer/ParametrizedDiagnosticAnalyzer.cs b/analyzers/src/SonarAnalyzer.Common/DiagnosticAnalyzer/ParametrizedDiagnosticAnalyzer.cs index cb3d06010dd..46f7ea5533a 100644 --- a/analyzers/src/SonarAnalyzer.Common/DiagnosticAnalyzer/ParametrizedDiagnosticAnalyzer.cs +++ b/analyzers/src/SonarAnalyzer.Common/DiagnosticAnalyzer/ParametrizedDiagnosticAnalyzer.cs @@ -32,7 +32,7 @@ protected sealed override void Initialize(SonarAnalysisContext context) context.RegisterCompilationStartAction( c => { - ParameterLoader.SetParameterValues(this, c.SonarLintFile()); + ParameterLoader.SetParameterValues(this, c.SonarLintXml()); parameterContext.ExecutePostponedActions(c); }); } diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs index 77cfddc9c1e..3f9af7d7d45 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/SonarLintXmlReader.cs @@ -100,8 +100,8 @@ private static SonarLintXml ParseContent(SourceText sonarLintXml) private string ReadSettingsProperty(string property) => sonarLintXml is { Settings: { } settings } - ? settings.Where(x => x.Key.Equals(property)).Select(x => x.Value).FirstOrDefault() - : string.Empty; + ? settings.Where(x => x.Key.Equals(property)).Select(x => x.Value).FirstOrDefault() + : null; private static string[] ReadCommaSeparatedArray(string str) => string.IsNullOrEmpty(str) ? Array.Empty() : str.Split(','); diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index 65627b91f2f..c2508a645c2 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -60,7 +60,7 @@ protected void ReadParameters(SonarCompilationStartAnalysisContext context) } if (context.Options.SonarLintXml() != null && !string.IsNullOrEmpty(outPath)) { - var sonarLintXml = context.SonarLintFile(); + var sonarLintXml = context.SonarLintXml(); IgnoreHeaderComments = sonarLintXml.IgnoreHeaderComments(context.Compilation.Language); AnalyzeGeneratedCode = sonarLintXml.AnalyzeGeneratedCode(context.Compilation.Language); OutPath = Path.Combine(outPath, context.Compilation.Language == LanguageNames.CSharp ? "output-cs" : "output-vbnet"); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs index f527dd092a9..bc44a11477e 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextBaseTest.cs @@ -179,7 +179,7 @@ public void SonarLintFile_LoadsExpectedValues(string language) var analyzerLanguage = language == "cs" ? AnalyzerLanguage.CSharp : AnalyzerLanguage.VisualBasic; var (compilation, _) = CreateDummyCompilation(analyzerLanguage, "ExtraEmptyFile"); var options = AnalysisScaffolding.CreateOptions($"ResourceTests\\SonarLintXml\\All_properties_{language}\\SonarLint.xml"); - var sut = CreateSut(compilation, options).SonarLintFile(); + var sut = CreateSut(compilation, options).SonarLintXml(); sut.IgnoreHeaderComments(analyzerLanguage.LanguageName).Should().BeTrue(); sut.AnalyzeGeneratedCode(analyzerLanguage.LanguageName).Should().BeFalse(); @@ -204,8 +204,8 @@ public void SonarLintFile_UsesCachedValue() var options = AnalysisScaffolding.CreateOptions("ResourceTests\\SonarLintXml\\All_properties_cs\\SonarLint.xml"); var firstSut = CreateSut(options); var secondSut = CreateSut(options); - var firstFile = firstSut.SonarLintFile(); - var secondFile = secondSut.SonarLintFile(); + var firstFile = firstSut.SonarLintXml(); + var secondFile = secondSut.SonarLintXml(); secondFile.Should().BeSameAs(firstFile); } @@ -215,8 +215,8 @@ public void SonarLintFile_WhenFileChanges_RebuildsCache() { var firstOptions = AnalysisScaffolding.CreateOptions("ResourceTests\\SonarLintXml\\All_properties_cs\\SonarLint.xml"); var secondOptions = AnalysisScaffolding.CreateOptions("ResourceTests\\SonarLintXml\\All_properties_vbnet\\SonarLint.xml"); - var firstFile = CreateSut(firstOptions).SonarLintFile(); - var secondFile = CreateSut(secondOptions).SonarLintFile(); + var firstFile = CreateSut(firstOptions).SonarLintXml(); + var secondFile = CreateSut(secondOptions).SonarLintXml(); secondFile.Should().NotBeSameAs(firstFile); } @@ -229,14 +229,14 @@ public void SonarLintFile_WhenFileChanges_RebuildsCache() [DataRow("path//SonarLint.xmla")] // different extension public void SonarLintFile_WhenAdditionalFileNotPresent_ReturnsDefaultValues(string folder) { - var sut = CreateSut(AnalysisScaffolding.CreateOptions(folder)).SonarLintFile(); + var sut = CreateSut(AnalysisScaffolding.CreateOptions(folder)).SonarLintXml(); CheckSonarLintXmlDefaultValues(sut); } [TestMethod] public void SonarLintFile_WhenInvalidXml_ReturnsDefaultValues() { - var sut = CreateSut(AnalysisScaffolding.CreateOptions("ResourceTests\\SonarLintXml\\Invalid_Xml\\SonarLint.xml")).SonarLintFile(); + var sut = CreateSut(AnalysisScaffolding.CreateOptions("ResourceTests\\SonarLintXml\\Invalid_Xml\\SonarLint.xml")).SonarLintXml(); CheckSonarLintXmlDefaultValues(sut); } @@ -245,7 +245,7 @@ public void SonarLintFile_WhenFileIsMissing_ThrowException() { var sut = CreateSut(AnalysisScaffolding.CreateOptions("ThisPathDoesNotExist\\SonarLint.xml")); - sut.Invoking(x => x.SonarLintFile()) + sut.Invoking(x => x.SonarLintXml()) .Should() .Throw() .WithMessage("File 'SonarLint.xml' has been added as an AdditionalFile but could not be read and parsed."); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs index fb9a2ff026c..5d599cd21c9 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/ParameterLoaderTest.cs @@ -41,7 +41,7 @@ public void SetParameterValues_WithInvalidSonarLintPath_DoesNotPopulateParameter var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.Maximum.Should().Be(3); // Default value @@ -57,7 +57,7 @@ public void SetParameterValues_WithValidSonarLintPath_PopulatesProperties(string var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.Maximum.Should().Be(1); // Value from the xml file @@ -71,7 +71,7 @@ public void SetParameterValues_SonarLintFileWithIntParameterType_PopulatesProper var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.Maximum.Should().Be(1); // Value from the xml file @@ -87,7 +87,7 @@ public void SetParameterValues_SonarLintFileWithStringParameterType_PopulatesPro var analyzer = new EnumNameShouldFollowRegex(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.FlagsEnumNamePattern.Should().Be(parameterValue); // value from XML file @@ -103,7 +103,7 @@ public void SetParameterValues_SonarLintFileWithBooleanParameterType_PopulatesPr var analyzer = new CheckFileLicense(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.IsRegularExpression.Should().Be(parameterValue); // value from XML file @@ -117,7 +117,7 @@ public void SetParameterValues_SonarLintFileWithoutRuleParameters_DoesNotPopulat var analyzer = new LineLength(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.Maximum.Should().Be(200); // Default value @@ -149,7 +149,7 @@ public void SetParameterValues_CalledTwiceAfterChangeInConfigFile_UpdatesPropert var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); analyzer.Maximum.Should().Be(maxValue); // Modify the in-memory additional file @@ -159,7 +159,7 @@ public void SetParameterValues_CalledTwiceAfterChangeInConfigFile_UpdatesPropert var modifiedFilePath = TestHelper.WriteFile(TestContext, "SonarLint.xml", modifiedSonarLintXml); compilation = CreateCompilationWithOption(modifiedFilePath); - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); analyzer.Maximum.Should().Be(maxValue); } @@ -174,7 +174,7 @@ public void SetParameterValues_WithMalformedXml_DoesNotPopulateProperties(string var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.Maximum.Should().Be(3); // Default value @@ -190,7 +190,7 @@ public void SetParameterValues_SonarLintFileWithStringInsteadOfIntParameterType_ var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.Maximum.Should().Be(3); // Default value @@ -206,7 +206,7 @@ public void SetParameterValues_SonarLintFileWithStringInsteadOfBooleanParameterT var analyzer = new CheckFileLicense(); // Cannot use mock because we use reflection to find properties. // Act - ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintFile()); + ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.IsRegularExpression.Should().BeFalse(); // Default value