From 490ee42f9c8762096be02fc0571d2a7e2d0f3479 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Tue, 20 Dec 2022 17:40:32 +0100 Subject: [PATCH 01/32] Use RegisterCompilationStartAction --- .../Rules/Utilities/LogAnalyzerBase.cs | 2 +- .../Rules/Utilities/UtilityAnalyzerBase.cs | 53 ++++++++++++------- .../Utilities/FileMetadataAnalyzerTest.cs | 17 +++--- .../Utilities/UtilityAnalyzerBaseTest.cs | 2 +- 4 files changed, 43 insertions(+), 31 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/LogAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/LogAnalyzerBase.cs index bceffd1e4f6..fcc73750d13 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/LogAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/LogAnalyzerBase.cs @@ -39,7 +39,7 @@ protected sealed override IEnumerable CreateAnalysisMessages(SonarCompi new[] { new LogInfo { Severity = LogSeverity.Info, Text = "Roslyn version: " + typeof(SyntaxNode).Assembly.GetName().Version }, - new LogInfo { Severity = LogSeverity.Info, Text = "Language version: " + LanguageVersion(c.Compilation) }, + new LogInfo { Severity = LogSeverity.Info, Text = "Language version: " + LanguageVersion(compilation) }, new LogInfo { Severity = LogSeverity.Info, Text = "Concurrent execution: " + (IsConcurrentExecutionEnabled() ? "enabled" : "disabled") } }; diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index 0fe145f7d57..d268201bc7c 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -50,22 +50,22 @@ internal static TextRange GetTextRange(FileLinePositionSpan lineSpan) => EndOffset = lineSpan.EndLinePosition.Character }; - protected void ReadParameters(SonarCompilationReportingContext c) + protected void ReadParameters(SonarAnalysisContext context, AnalyzerOptions options, Compilation compilation) { - var settings = c.Options.ParseSonarLintXmlSettings(); - var outPath = c.ProjectConfiguration().OutPath; + var settings = PropertiesHelper.GetSettings(options).ToList(); + var outPath = context.ProjectConfiguration(options).OutPath; // For backward compatibility with S4MSB <= 5.0 - if (outPath == null && c.Options.ProjectOutFolderPath() is { } projectOutFolderAdditionalFile) + if (outPath == null && options.ProjectOutFolderPath() is { } projectOutFolderAdditionalFile) { outPath = projectOutFolderAdditionalFile.GetText().ToString().TrimEnd(); } if (settings.Any() && !string.IsNullOrEmpty(outPath)) { - IgnoreHeaderComments = PropertiesHelper.ReadIgnoreHeaderCommentsProperty(settings, c.Compilation.Language); - AnalyzeGeneratedCode = PropertiesHelper.ReadAnalyzeGeneratedCodeProperty(settings, c.Compilation.Language); - OutPath = Path.Combine(outPath, c.Compilation.Language == LanguageNames.CSharp ? "output-cs" : "output-vbnet"); + IgnoreHeaderComments = PropertiesHelper.ReadIgnoreHeaderCommentsProperty(settings, compilation.Language); + AnalyzeGeneratedCode = PropertiesHelper.ReadAnalyzeGeneratedCodeProperty(settings, compilation.Language); + OutPath = Path.Combine(outPath, compilation.Language == LanguageNames.CSharp ? "output-cs" : "output-vbnet"); IsAnalyzerEnabled = true; - IsTestProject = c.IsTestProject(); + IsTestProject = context.IsTestProject(compilation, options); } } } @@ -82,36 +82,53 @@ public abstract class UtilityAnalyzerBase : UtilityAnalyz protected virtual bool AnalyzeUnchangedFiles => false; - protected virtual IEnumerable CreateAnalysisMessages(SonarCompilationReportingContext c) => Enumerable.Empty(); + protected virtual IEnumerable CreateAnalysisMessages(Compilation compilation) => Enumerable.Empty(); protected UtilityAnalyzerBase(string diagnosticId, string title) : base(diagnosticId, title) { } protected sealed override void Initialize(SonarAnalysisContext context) => - context.RegisterCompilationAction(c => + context.RegisterCompilationStartAction(start => + { + ReadParameters(context, start.Options, start.Compilation); + if (!IsAnalyzerEnabled) + { + return; + } + List treeMessages = new(); + start.RegisterSemanticModelAction(model => { - ReadParameters(c); + ReadParameters(context, model.Options, model.SemanticModel.Compilation); if (!IsAnalyzerEnabled) { return; } + var syntaxTree = model.SemanticModel.SyntaxTree; + if (ShouldGenerateMetrics(start, syntaxTree)) + { + treeMessages.Add(CreateMessage(syntaxTree, model.SemanticModel)); + } + }); + + start.RegisterCompilationEndAction(end => + { + var analysisMessages = CreateAnalysisMessages(end.Compilation).ToList(); - var treeMessages = c.Compilation.SyntaxTrees - .Where(x => ShouldGenerateMetrics(c, x)) - .Select(x => CreateMessage(x, c.Compilation.GetSemanticModel(x))); - var messages = CreateAnalysisMessages(c) + var allMessages = analysisMessages .Concat(treeMessages) .WhereNotNull() .ToArray(); + lock (FileWriteLock) { Directory.CreateDirectory(OutPath); using var stream = File.Create(Path.Combine(OutPath, FileName)); - foreach (var message in messages) + foreach (var message in allMessages) { message.WriteDelimitedTo(stream); } } }); + }); protected virtual bool ShouldGenerateMetrics(SyntaxTree tree) => // The results of Metrics and CopyPasteToken analyzers are not needed for Test projects yet the plugin side expects the protobuf files, so we create empty ones. @@ -119,8 +136,8 @@ protected virtual bool ShouldGenerateMetrics(SyntaxTree tree) => && FileExtensionWhitelist.Contains(Path.GetExtension(tree.FilePath)) && (AnalyzeGeneratedCode || !Language.GeneratedCodeRecognizer.IsGenerated(tree)); - private bool ShouldGenerateMetrics(SonarCompilationReportingContext context, SyntaxTree tree) => - (AnalyzeUnchangedFiles || !context.IsUnchanged(tree)) + private bool ShouldGenerateMetrics(CompilationStartAnalysisContext context, SyntaxTree tree) => + (AnalyzeUnchangedFiles || !SonarAnalysisContext.IsUnchanged(context.TryGetValue, tree, context.Compilation, context.Options)) && ShouldGenerateMetrics(tree); } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/FileMetadataAnalyzerTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/FileMetadataAnalyzerTest.cs index dd796cbc97d..5088d5a3290 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/FileMetadataAnalyzerTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/FileMetadataAnalyzerTest.cs @@ -76,17 +76,12 @@ public void NotAutogenerated(ProjectType projectType) CreateBuilder(projectType, notAutogeneratedFiles) .WithSonarProjectConfigPath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, projectType)) .VerifyUtilityAnalyzer(messages => - { - messages[0].IsGenerated.Should().BeTrue(); - messages[0].FilePath.Should().Be("ExtraEmptyFile.g.cs"); - - for (var i = 0; i < notAutogeneratedFiles.Length; i++) + messages.Should().HaveCount(notAutogeneratedFiles.Length + 1) // +1 for ExtraEmptyFile.g.cs + .And.Contain(new FileMetadataInfo { IsGenerated = true, FilePath = "ExtraEmptyFile.g.cs" }) + .And.Contain(notAutogeneratedFiles.Select(expected => new FileMetadataInfo { - var message = messages[i + 1]; // The first message is for ExtraEmptyFile.g.cs, then is our list - message.IsGenerated.Should().BeFalse(); - message.FilePath.Should().Be(BasePath + notAutogeneratedFiles[i]); - } - }); + FilePath = BasePath + expected, + }))); } [DataTestMethod] @@ -116,7 +111,7 @@ private void VerifyAllFilesAreGenerated(ProjectType projectType, string[] projec .VerifyUtilityAnalyzer(messages => { messages.Should().AllBeEquivalentTo(new { IsGenerated = true }); - messages.Should().Equal(autogeneratedFiles, equalityComparison: (m, a) => m.FilePath.EndsWith(a)); + messages.Should().SatisfyRespectively(autogeneratedFiles.Select>(expected => actual => actual.FilePath.EndsWith(expected))); }); private VerifierBuilder CreateBuilder(ProjectType projectType, params string[] projectFiles) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs index b6bc2cebaa2..d213b2a108c 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs @@ -137,7 +137,7 @@ public TestUtilityAnalyzer(string language, params string[] additionalPaths) : b _ => throw new InvalidOperationException($"Unexpected {nameof(language)}: {language}") }; var c = new CompilationAnalysisContext(compilation, new AnalyzerOptions(additionalFiles), null, null, default); - ReadParameters(new(AnalysisScaffolding.CreateSonarAnalysisContext(), c)); + ReadParameters(context, new AnalyzerOptions(additionalFiles), compilation); } protected override void Initialize(SonarAnalysisContext context) => From 3c81804cd5fbb585c69ad9015e304f6d2cec056d Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Tue, 20 Dec 2022 17:46:21 +0100 Subject: [PATCH 02/32] Remove redundant check --- .../Rules/Utilities/UtilityAnalyzerBase.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index d268201bc7c..1d6a35f7428 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -97,11 +97,6 @@ protected sealed override void Initialize(SonarAnalysisContext context) => List treeMessages = new(); start.RegisterSemanticModelAction(model => { - ReadParameters(context, model.Options, model.SemanticModel.Compilation); - if (!IsAnalyzerEnabled) - { - return; - } var syntaxTree = model.SemanticModel.SyntaxTree; if (ShouldGenerateMetrics(start, syntaxTree)) { From 085fb994762a3008bb25c8b3b3e289cb9c6ce242 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Tue, 20 Dec 2022 17:46:32 +0100 Subject: [PATCH 03/32] Check for IsGenerated in test --- .../Rules/Utilities/FileMetadataAnalyzerTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/FileMetadataAnalyzerTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/FileMetadataAnalyzerTest.cs index 5088d5a3290..ac37555445f 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/FileMetadataAnalyzerTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/FileMetadataAnalyzerTest.cs @@ -80,6 +80,7 @@ public void NotAutogenerated(ProjectType projectType) .And.Contain(new FileMetadataInfo { IsGenerated = true, FilePath = "ExtraEmptyFile.g.cs" }) .And.Contain(notAutogeneratedFiles.Select(expected => new FileMetadataInfo { + IsGenerated = false, FilePath = BasePath + expected, }))); } From f47bb6a22900c1f07bc5351047f7fb54198877ee Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Tue, 20 Dec 2022 18:12:42 +0100 Subject: [PATCH 04/32] Use ConcurrentStack and don't take permanent dependency on startContext --- .../Rules/Utilities/UtilityAnalyzerBase.cs | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index 1d6a35f7428..0460bf6a7c0 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using System.Collections.Concurrent; using System.IO; using Google.Protobuf; using SonarAnalyzer.Protobuf; @@ -87,26 +88,30 @@ public abstract class UtilityAnalyzerBase : UtilityAnalyz protected UtilityAnalyzerBase(string diagnosticId, string title) : base(diagnosticId, title) { } protected sealed override void Initialize(SonarAnalysisContext context) => - context.RegisterCompilationStartAction(start => + context.RegisterCompilationStartAction(startContext => { - ReadParameters(context, start.Options, start.Compilation); + ReadParameters(context, startContext.Options, startContext.Compilation); if (!IsAnalyzerEnabled) { return; } - List treeMessages = new(); - start.RegisterSemanticModelAction(model => + + ConcurrentStack treeMessages = new(); + SonarAnalysisContext.TryGetValueDelegate tryGetValue = startContext.TryGetValue; + + startContext.RegisterSemanticModelAction(modelContext => { - var syntaxTree = model.SemanticModel.SyntaxTree; - if (ShouldGenerateMetrics(start, syntaxTree)) + var semanticModel = modelContext.SemanticModel; + var syntaxTree = semanticModel.SyntaxTree; + if (ShouldGenerateMetrics(tryGetValue, semanticModel.Compilation, modelContext.Options, syntaxTree)) { - treeMessages.Add(CreateMessage(syntaxTree, model.SemanticModel)); + treeMessages.Push(CreateMessage(syntaxTree, semanticModel)); } }); - start.RegisterCompilationEndAction(end => + startContext.RegisterCompilationEndAction(endContext => { - var analysisMessages = CreateAnalysisMessages(end.Compilation).ToList(); + var analysisMessages = CreateAnalysisMessages(endContext.Compilation); var allMessages = analysisMessages .Concat(treeMessages) @@ -121,6 +126,7 @@ protected sealed override void Initialize(SonarAnalysisContext context) => { message.WriteDelimitedTo(stream); } + stream.Close(); } }); }); @@ -131,8 +137,8 @@ protected virtual bool ShouldGenerateMetrics(SyntaxTree tree) => && FileExtensionWhitelist.Contains(Path.GetExtension(tree.FilePath)) && (AnalyzeGeneratedCode || !Language.GeneratedCodeRecognizer.IsGenerated(tree)); - private bool ShouldGenerateMetrics(CompilationStartAnalysisContext context, SyntaxTree tree) => - (AnalyzeUnchangedFiles || !SonarAnalysisContext.IsUnchanged(context.TryGetValue, tree, context.Compilation, context.Options)) + private bool ShouldGenerateMetrics(SonarAnalysisContext.TryGetValueDelegate tryGetValue, Compilation compilation, AnalyzerOptions options, SyntaxTree tree) => + (AnalyzeUnchangedFiles || !SonarAnalysisContext.IsUnchanged(tryGetValue, tree, compilation, options)) && ShouldGenerateMetrics(tree); } } From 76024bfbb1b5fa93068cb331a337a0fce1b9415b Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Tue, 20 Dec 2022 18:32:25 +0100 Subject: [PATCH 05/32] Use BeEquivalentTo instead of Contains/Count --- .../Rules/Utilities/FileMetadataAnalyzerTest.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/FileMetadataAnalyzerTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/FileMetadataAnalyzerTest.cs index ac37555445f..9f27d778f32 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/FileMetadataAnalyzerTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/FileMetadataAnalyzerTest.cs @@ -76,13 +76,15 @@ public void NotAutogenerated(ProjectType projectType) CreateBuilder(projectType, notAutogeneratedFiles) .WithSonarProjectConfigPath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, projectType)) .VerifyUtilityAnalyzer(messages => - messages.Should().HaveCount(notAutogeneratedFiles.Length + 1) // +1 for ExtraEmptyFile.g.cs - .And.Contain(new FileMetadataInfo { IsGenerated = true, FilePath = "ExtraEmptyFile.g.cs" }) - .And.Contain(notAutogeneratedFiles.Select(expected => new FileMetadataInfo - { - IsGenerated = false, - FilePath = BasePath + expected, - }))); + messages.Should().BeEquivalentTo(notAutogeneratedFiles.Select(expected => new FileMetadataInfo + { + IsGenerated = false, + FilePath = BasePath + expected, + }).Append(new FileMetadataInfo + { + IsGenerated = true, + FilePath = "ExtraEmptyFile.g.cs" + }))); } [DataTestMethod] From e3e175401fa8cf2b330345a68982885ab8468b7d Mon Sep 17 00:00:00 2001 From: Martin Strecker <103252490+martin-strecker-sonarsource@users.noreply.github.com> Date: Mon, 2 Jan 2023 15:49:56 +0100 Subject: [PATCH 06/32] Apply suggestions from code review Co-authored-by: Pavel Mikula <57188685+pavel-mikula-sonarsource@users.noreply.github.com> --- .../Rules/Utilities/UtilityAnalyzerBase.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index 0460bf6a7c0..82bbd951381 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -101,8 +101,7 @@ protected sealed override void Initialize(SonarAnalysisContext context) => startContext.RegisterSemanticModelAction(modelContext => { - var semanticModel = modelContext.SemanticModel; - var syntaxTree = semanticModel.SyntaxTree; + var tree = semanticModel.SyntaxTree; if (ShouldGenerateMetrics(tryGetValue, semanticModel.Compilation, modelContext.Options, syntaxTree)) { treeMessages.Push(CreateMessage(syntaxTree, semanticModel)); @@ -113,7 +112,7 @@ protected sealed override void Initialize(SonarAnalysisContext context) => { var analysisMessages = CreateAnalysisMessages(endContext.Compilation); - var allMessages = analysisMessages + var allMessages = CreateAnalysisMessages(endContext.Compilation) .Concat(treeMessages) .WhereNotNull() .ToArray(); @@ -126,7 +125,6 @@ protected sealed override void Initialize(SonarAnalysisContext context) => { message.WriteDelimitedTo(stream); } - stream.Close(); } }); }); From 701acc376fcc9215a806056ad207eeca7d129770 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Mon, 2 Jan 2023 15:54:07 +0100 Subject: [PATCH 07/32] Clean up after suggestions --- .../Rules/Utilities/UtilityAnalyzerBase.cs | 8 +++----- .../Rules/Utilities/UtilityAnalyzerBaseTest.cs | 1 - 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index 82bbd951381..bd7cf691d03 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -101,17 +101,15 @@ protected sealed override void Initialize(SonarAnalysisContext context) => startContext.RegisterSemanticModelAction(modelContext => { - var tree = semanticModel.SyntaxTree; - if (ShouldGenerateMetrics(tryGetValue, semanticModel.Compilation, modelContext.Options, syntaxTree)) + var tree = modelContext.SemanticModel.SyntaxTree; + if (ShouldGenerateMetrics(tryGetValue, modelContext.SemanticModel.Compilation, modelContext.Options, tree)) { - treeMessages.Push(CreateMessage(syntaxTree, semanticModel)); + treeMessages.Push(CreateMessage(tree, modelContext.SemanticModel)); } }); startContext.RegisterCompilationEndAction(endContext => { - var analysisMessages = CreateAnalysisMessages(endContext.Compilation); - var allMessages = CreateAnalysisMessages(endContext.Compilation) .Concat(treeMessages) .WhereNotNull() diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs index d213b2a108c..8b3ff396aa9 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs @@ -136,7 +136,6 @@ public TestUtilityAnalyzer(string language, params string[] additionalPaths) : b LanguageNames.VisualBasic => VisualBasicCompilation.Create(null), _ => throw new InvalidOperationException($"Unexpected {nameof(language)}: {language}") }; - var c = new CompilationAnalysisContext(compilation, new AnalyzerOptions(additionalFiles), null, null, default); ReadParameters(context, new AnalyzerOptions(additionalFiles), compilation); } From 0bd463e8d2017357d9caf1caa23b634c5ca791b9 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Tue, 17 Jan 2023 18:41:40 +0100 Subject: [PATCH 08/32] Make it compile --- .../SonarAnalysisContextBase.cs | 57 ++++++++++--------- .../SonarCompilationStartAnalysisContext.cs | 2 + .../SonarSematicModelReportingContext.cs | 39 +++++++++++++ .../Helpers/ReportingContext.cs | 3 + .../Rules/Utilities/LogAnalyzerBase.cs | 2 +- .../Rules/Utilities/UtilityAnalyzerBase.cs | 31 +++++----- .../Utilities/UtilityAnalyzerBaseTest.cs | 17 +++++- 7 files changed, 104 insertions(+), 47 deletions(-) create mode 100644 analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarSematicModelReportingContext.cs diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs index b0ef9dc8e76..41a463221f6 100644 --- a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs @@ -24,43 +24,22 @@ namespace SonarAnalyzer.AnalysisContext; -public class SonarAnalysisContextBase +public abstract class SonarAnalysisContextBase { 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 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 -{ - public abstract SyntaxTree Tree { get; } - public abstract Compilation Compilation { get; } - public abstract AnalyzerOptions Options { get; } - public abstract CancellationToken Cancel { get; } - - public SonarAnalysisContext AnalysisContext { get; } - public TContext Context { get; } - - protected SonarAnalysisContextBase(SonarAnalysisContext analysisContext, TContext context) + protected SonarAnalysisContextBase(SonarAnalysisContext analysisContext) { AnalysisContext = analysisContext ?? throw new ArgumentNullException(nameof(analysisContext)); - Context = context; } - /// 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)); + public SonarAnalysisContext AnalysisContext { get; } + + public abstract AnalyzerOptions Options { get; } + public abstract Compilation Compilation { get; } /// /// Reads configuration from SonarProjectConfig.xml file and caches the result for scope of this analysis. @@ -89,6 +68,30 @@ public bool IsTestProject() : projectType == ProjectType.Test; // Scanner >= 5.1 does authoritative decision that we follow } + 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 +{ + public abstract SyntaxTree Tree { get; } + public abstract CancellationToken Cancel { get; } + public TContext Context { get; } + + protected SonarAnalysisContextBase(SonarAnalysisContext analysisContext, TContext context) : base(analysisContext) + { + Context = context; + } + + /// 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)); + public bool IsUnchanged(SyntaxTree tree) => UnchangedFilesCache.GetValue(Compilation, _ => CreateUnchangedFilesHashSet()).Contains(tree.FilePath); diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarCompilationStartAnalysisContext.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarCompilationStartAnalysisContext.cs index 1e38f8ace02..eabc3d8b65a 100644 --- a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarCompilationStartAnalysisContext.cs +++ b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarCompilationStartAnalysisContext.cs @@ -32,6 +32,8 @@ internal SonarCompilationStartAnalysisContext(SonarAnalysisContext analysisConte public void RegisterSymbolAction(Action action, params SymbolKind[] symbolKinds) => Context.RegisterSymbolAction(x => action(new(AnalysisContext, x)), symbolKinds); + public void RegisterSemanticModelAction(Action action) => + Context.RegisterSemanticModelAction(x => action(new(AnalysisContext, x))); public void RegisterCompilationEndAction(Action action) => Context.RegisterCompilationEndAction(x => action(new(AnalysisContext, x))); diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarSematicModelReportingContext.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarSematicModelReportingContext.cs new file mode 100644 index 00000000000..e0c43d9925d --- /dev/null +++ b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarSematicModelReportingContext.cs @@ -0,0 +1,39 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2022 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. + */ + +namespace SonarAnalyzer.AnalysisContext; + +public sealed class SonarSematicModelReportingContext : SonarCompilationReportingContextBase +{ + internal SonarSematicModelReportingContext(SonarAnalysisContext analysisContext, SemanticModelAnalysisContext context) : base(analysisContext, context) { } + public override SyntaxTree Tree => Context.SemanticModel.SyntaxTree; + + public override Compilation Compilation => Context.SemanticModel.Compilation; + + public override AnalyzerOptions Options => Context.Options; + + public SemanticModel SemanticModel => Context.SemanticModel; + + public override CancellationToken Cancel => Context.CancellationToken; + + private protected override ReportingContext CreateReportingContext(Diagnostic diagnostic) => + new(this, diagnostic); + +} diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/ReportingContext.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/ReportingContext.cs index 37ba075650c..a5f2990b702 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/ReportingContext.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/ReportingContext.cs @@ -43,6 +43,9 @@ public ReportingContext(SonarSymbolReportingContext context, Diagnostic diagnost public ReportingContext(SonarCodeBlockReportingContext context, Diagnostic diagnostic) : this(diagnostic, context.Context.ReportDiagnostic, context.Compilation, context.Tree) { } + public ReportingContext(SonarSematicModelReportingContext context, Diagnostic diagnostic) + : this(diagnostic, context.Context.ReportDiagnostic, context.Compilation, context.Tree) { } + private ReportingContext(Diagnostic diagnostic, Action roslynReportDiagnostic, Compilation compilation, diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/LogAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/LogAnalyzerBase.cs index fcc73750d13..bceffd1e4f6 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/LogAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/LogAnalyzerBase.cs @@ -39,7 +39,7 @@ protected sealed override IEnumerable CreateAnalysisMessages(SonarCompi new[] { new LogInfo { Severity = LogSeverity.Info, Text = "Roslyn version: " + typeof(SyntaxNode).Assembly.GetName().Version }, - new LogInfo { Severity = LogSeverity.Info, Text = "Language version: " + LanguageVersion(compilation) }, + new LogInfo { Severity = LogSeverity.Info, Text = "Language version: " + LanguageVersion(c.Compilation) }, new LogInfo { Severity = LogSeverity.Info, Text = "Concurrent execution: " + (IsConcurrentExecutionEnabled() ? "enabled" : "disabled") } }; diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index bd7cf691d03..446199dad34 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -51,22 +51,22 @@ internal static TextRange GetTextRange(FileLinePositionSpan lineSpan) => EndOffset = lineSpan.EndLinePosition.Character }; - protected void ReadParameters(SonarAnalysisContext context, AnalyzerOptions options, Compilation compilation) + protected void ReadParameters(SonarAnalysisContextBase c) { - var settings = PropertiesHelper.GetSettings(options).ToList(); - var outPath = context.ProjectConfiguration(options).OutPath; + var settings = c.Options.ParseSonarLintXmlSettings(); + var outPath = c.ProjectConfiguration().OutPath; // For backward compatibility with S4MSB <= 5.0 - if (outPath == null && options.ProjectOutFolderPath() is { } projectOutFolderAdditionalFile) + if (outPath == null && c.Options.ProjectOutFolderPath() is { } projectOutFolderAdditionalFile) { outPath = projectOutFolderAdditionalFile.GetText().ToString().TrimEnd(); } if (settings.Any() && !string.IsNullOrEmpty(outPath)) { - IgnoreHeaderComments = PropertiesHelper.ReadIgnoreHeaderCommentsProperty(settings, compilation.Language); - AnalyzeGeneratedCode = PropertiesHelper.ReadAnalyzeGeneratedCodeProperty(settings, compilation.Language); - OutPath = Path.Combine(outPath, compilation.Language == LanguageNames.CSharp ? "output-cs" : "output-vbnet"); + IgnoreHeaderComments = PropertiesHelper.ReadIgnoreHeaderCommentsProperty(settings, c.Compilation.Language); + AnalyzeGeneratedCode = PropertiesHelper.ReadAnalyzeGeneratedCodeProperty(settings, c.Compilation.Language); + OutPath = Path.Combine(outPath, c.Compilation.Language == LanguageNames.CSharp ? "output-cs" : "output-vbnet"); IsAnalyzerEnabled = true; - IsTestProject = context.IsTestProject(compilation, options); + IsTestProject = c.IsTestProject(); } } } @@ -83,26 +83,25 @@ public abstract class UtilityAnalyzerBase : UtilityAnalyz protected virtual bool AnalyzeUnchangedFiles => false; - protected virtual IEnumerable CreateAnalysisMessages(Compilation compilation) => Enumerable.Empty(); + protected virtual IEnumerable CreateAnalysisMessages(SonarCompilationReportingContext c) => Enumerable.Empty(); protected UtilityAnalyzerBase(string diagnosticId, string title) : base(diagnosticId, title) { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(startContext => { - ReadParameters(context, startContext.Options, startContext.Compilation); + ReadParameters(startContext); if (!IsAnalyzerEnabled) { return; } ConcurrentStack treeMessages = new(); - SonarAnalysisContext.TryGetValueDelegate tryGetValue = startContext.TryGetValue; startContext.RegisterSemanticModelAction(modelContext => { - var tree = modelContext.SemanticModel.SyntaxTree; - if (ShouldGenerateMetrics(tryGetValue, modelContext.SemanticModel.Compilation, modelContext.Options, tree)) + var tree = modelContext.Tree; + if (ShouldGenerateMetrics(modelContext, modelContext.Tree)) { treeMessages.Push(CreateMessage(tree, modelContext.SemanticModel)); } @@ -110,7 +109,7 @@ protected sealed override void Initialize(SonarAnalysisContext context) => startContext.RegisterCompilationEndAction(endContext => { - var allMessages = CreateAnalysisMessages(endContext.Compilation) + var allMessages = CreateAnalysisMessages(endContext) .Concat(treeMessages) .WhereNotNull() .ToArray(); @@ -133,8 +132,8 @@ protected virtual bool ShouldGenerateMetrics(SyntaxTree tree) => && FileExtensionWhitelist.Contains(Path.GetExtension(tree.FilePath)) && (AnalyzeGeneratedCode || !Language.GeneratedCodeRecognizer.IsGenerated(tree)); - private bool ShouldGenerateMetrics(SonarAnalysisContext.TryGetValueDelegate tryGetValue, Compilation compilation, AnalyzerOptions options, SyntaxTree tree) => - (AnalyzeUnchangedFiles || !SonarAnalysisContext.IsUnchanged(tryGetValue, tree, compilation, options)) + private bool ShouldGenerateMetrics(SonarSematicModelReportingContext context, SyntaxTree tree) => + (AnalyzeUnchangedFiles || !context.IsUnchanged(tree)) && ShouldGenerateMetrics(tree); } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs index 8b3ff396aa9..2f038138c00 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs @@ -24,8 +24,6 @@ using SonarAnalyzer.AnalysisContext; using SonarAnalyzer.Common; using SonarAnalyzer.Rules; -using SonarAnalyzer.UnitTest.AnalysisContext; -using SonarAnalyzer.UnitTest.Helpers; namespace SonarAnalyzer.UnitTest.Rules.Utilities { @@ -136,11 +134,24 @@ public TestUtilityAnalyzer(string language, params string[] additionalPaths) : b LanguageNames.VisualBasic => VisualBasicCompilation.Create(null), _ => throw new InvalidOperationException($"Unexpected {nameof(language)}: {language}") }; - ReadParameters(context, new AnalyzerOptions(additionalFiles), compilation); + ReadParameters(new SonarAnalysisTestContext(AnalysisScaffolding.CreateSonarAnalysisContext(), new AnalyzerOptions(additionalFiles), compilation)); } protected override void Initialize(SonarAnalysisContext context) => throw new NotImplementedException(); + + private class SonarAnalysisTestContext : SonarAnalysisContextBase + { + public SonarAnalysisTestContext(SonarAnalysisContext context, AnalyzerOptions options, Compilation compilation) : base(context) + { + Options = options; + Compilation = compilation; + } + + public override AnalyzerOptions Options { get; } + + public override Compilation Compilation { get; } + } } } } From a7aba58f618a3813ff153bd208e52a5110da6d31 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Tue, 17 Jan 2023 18:47:52 +0100 Subject: [PATCH 09/32] Some clean-up. --- .../Rules/Utilities/UtilityAnalyzerBase.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index 446199dad34..6aa96f108ac 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -100,10 +100,9 @@ protected sealed override void Initialize(SonarAnalysisContext context) => startContext.RegisterSemanticModelAction(modelContext => { - var tree = modelContext.Tree; if (ShouldGenerateMetrics(modelContext, modelContext.Tree)) { - treeMessages.Push(CreateMessage(tree, modelContext.SemanticModel)); + treeMessages.Push(CreateMessage(modelContext.Tree, modelContext.SemanticModel)); } }); @@ -132,8 +131,8 @@ protected virtual bool ShouldGenerateMetrics(SyntaxTree tree) => && FileExtensionWhitelist.Contains(Path.GetExtension(tree.FilePath)) && (AnalyzeGeneratedCode || !Language.GeneratedCodeRecognizer.IsGenerated(tree)); - private bool ShouldGenerateMetrics(SonarSematicModelReportingContext context, SyntaxTree tree) => - (AnalyzeUnchangedFiles || !context.IsUnchanged(tree)) - && ShouldGenerateMetrics(tree); + private bool ShouldGenerateMetrics(SonarSematicModelReportingContext context) => + (AnalyzeUnchangedFiles || !context.IsUnchanged(context.Tree)) + && ShouldGenerateMetrics(context.Tree); } } From c32cbd4116a1e5bf3684e5863f2251f8899371f2 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Tue, 17 Jan 2023 18:54:14 +0100 Subject: [PATCH 10/32] Fix call site --- .../SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index 6aa96f108ac..f5e580ee014 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -100,7 +100,7 @@ protected sealed override void Initialize(SonarAnalysisContext context) => startContext.RegisterSemanticModelAction(modelContext => { - if (ShouldGenerateMetrics(modelContext, modelContext.Tree)) + if (ShouldGenerateMetrics(modelContext)) { treeMessages.Push(CreateMessage(modelContext.Tree, modelContext.SemanticModel)); } From bd81944fc8ea819c0ea3107639b0acc9cd054881 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Mon, 23 Jan 2023 20:27:59 +0100 Subject: [PATCH 11/32] Formatting and smaller changes --- .../SonarCompilationStartAnalysisContext.cs | 1 + .../SonarSematicModelReportingContext.cs | 10 +++------- .../Rules/Utilities/UtilityAnalyzerBase.cs | 4 +--- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarCompilationStartAnalysisContext.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarCompilationStartAnalysisContext.cs index eabc3d8b65a..bd0373b0677 100644 --- a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarCompilationStartAnalysisContext.cs +++ b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarCompilationStartAnalysisContext.cs @@ -34,6 +34,7 @@ public void RegisterSymbolAction(Action action, par public void RegisterSemanticModelAction(Action action) => Context.RegisterSemanticModelAction(x => action(new(AnalysisContext, x))); + public void RegisterCompilationEndAction(Action action) => Context.RegisterCompilationEndAction(x => action(new(AnalysisContext, x))); diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarSematicModelReportingContext.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarSematicModelReportingContext.cs index e0c43d9925d..6cfcc85043b 100644 --- a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarSematicModelReportingContext.cs +++ b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarSematicModelReportingContext.cs @@ -22,18 +22,14 @@ namespace SonarAnalyzer.AnalysisContext; public sealed class SonarSematicModelReportingContext : SonarCompilationReportingContextBase { - internal SonarSematicModelReportingContext(SonarAnalysisContext analysisContext, SemanticModelAnalysisContext context) : base(analysisContext, context) { } - public override SyntaxTree Tree => Context.SemanticModel.SyntaxTree; - + public override SyntaxTree Tree => SemanticModel.SyntaxTree; public override Compilation Compilation => Context.SemanticModel.Compilation; - public override AnalyzerOptions Options => Context.Options; - + public override CancellationToken Cancel => Context.CancellationToken; public SemanticModel SemanticModel => Context.SemanticModel; - public override CancellationToken Cancel => Context.CancellationToken; + internal SonarSematicModelReportingContext(SonarAnalysisContext analysisContext, SemanticModelAnalysisContext context) : base(analysisContext, context) { } private protected override ReportingContext CreateReportingContext(Diagnostic diagnostic) => new(this, diagnostic); - } diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index f5e580ee014..84be151350f 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -18,7 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System.Collections.Concurrent; using System.IO; using Google.Protobuf; using SonarAnalyzer.Protobuf; @@ -96,7 +95,7 @@ protected sealed override void Initialize(SonarAnalysisContext context) => return; } - ConcurrentStack treeMessages = new(); + Stack treeMessages = new(); startContext.RegisterSemanticModelAction(modelContext => { @@ -112,7 +111,6 @@ protected sealed override void Initialize(SonarAnalysisContext context) => .Concat(treeMessages) .WhereNotNull() .ToArray(); - lock (FileWriteLock) { Directory.CreateDirectory(OutPath); From 181fdf1614698442c54bd5a24f3968681caf7130 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Mon, 23 Jan 2023 20:42:55 +0100 Subject: [PATCH 12/32] Fix test assertions --- .../SonarCodeBlockReportingContextTest.cs | 12 ++++++------ .../SonarCodeBlockStartAnalysisContextTest.cs | 12 ++++++------ .../SonarCompilationReportingContextTest.cs | 6 +++--- .../SonarCompilationStartAnalysisContextTest.cs | 6 +++--- .../SonarSymbolReportingContextTest.cs | 8 ++++---- .../SonarSyntaxNodeReportingContextTest.cs | 12 ++++++------ .../SonarSyntaxTreeReportingContextTest.cs | 6 +++--- 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCodeBlockReportingContextTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCodeBlockReportingContextTest.cs index ed2cb8835b4..cca5540acbb 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCodeBlockReportingContextTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCodeBlockReportingContextTest.cs @@ -38,12 +38,12 @@ public void Properties_ArePropagated() var context = new CodeBlockAnalysisContext(codeBlock, owningSymbol, model, options, _ => { }, _ => true, cancel); var sut = new SonarCodeBlockReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context); - sut.Tree.Should().Be(codeBlock.SyntaxTree); - sut.Compilation.Should().Be(model.Compilation); - sut.Options.Should().Be(options); + sut.Tree.Should().BeSameAs(codeBlock.SyntaxTree); + sut.Compilation.Should().BeSameAs(model.Compilation); + sut.Options.Should().BeSameAs(options); sut.Cancel.Should().Be(cancel); - sut.CodeBlock.Should().Be(codeBlock); - sut.OwningSymbol.Should().Be(owningSymbol); - sut.SemanticModel.Should().Be(model); + sut.CodeBlock.Should().BeSameAs(codeBlock); + sut.OwningSymbol.Should().BeSameAs(owningSymbol); + sut.SemanticModel.Should().BeSameAs(model); } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCodeBlockStartAnalysisContextTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCodeBlockStartAnalysisContextTest.cs index 2ccfb0a4587..ec47ba9d376 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCodeBlockStartAnalysisContextTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCodeBlockStartAnalysisContextTest.cs @@ -38,12 +38,12 @@ public void Properties_ArePropagated() var context = new Mock>(codeBlock, owningSymbol, model, options, cancel).Object; var sut = new SonarCodeBlockStartAnalysisContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context); - sut.Tree.Should().Be(codeBlock.SyntaxTree); - sut.Compilation.Should().Be(model.Compilation); - sut.Options.Should().Be(options); + sut.Tree.Should().BeSameAs(codeBlock.SyntaxTree); + sut.Compilation.Should().BeSameAs(model.Compilation); + sut.Options.Should().BeSameAs(options); sut.Cancel.Should().Be(cancel); - sut.CodeBlock.Should().Be(codeBlock); - sut.OwningSymbol.Should().Be(owningSymbol); - sut.SemanticModel.Should().Be(model); + sut.CodeBlock.Should().BeSameAs(codeBlock); + sut.OwningSymbol.Should().BeSameAs(owningSymbol); + sut.SemanticModel.Should().BeSameAs(model); } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCompilationReportingContextTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCompilationReportingContextTest.cs index e676760cdcd..cd500aba52f 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCompilationReportingContextTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCompilationReportingContextTest.cs @@ -34,9 +34,9 @@ public void Properties_ArePropagated() var context = new CompilationAnalysisContext(model.Compilation, options, _ => { }, _ => true, cancel); var sut = new SonarCompilationReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context); - sut.Tree.Should().Be(tree); - sut.Compilation.Should().Be(model.Compilation); - sut.Options.Should().Be(options); + sut.Tree.Should().BeSameAs(tree); + sut.Compilation.Should().BeSameAs(model.Compilation); + sut.Options.Should().BeSameAs(options); sut.Cancel.Should().Be(cancel); } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCompilationStartAnalysisContextTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCompilationStartAnalysisContextTest.cs index fde4e28c0c7..a91a7f66909 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCompilationStartAnalysisContextTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCompilationStartAnalysisContextTest.cs @@ -35,9 +35,9 @@ public void Properties_ArePropagated() var context = new Mock(model.Compilation, options, cancel).Object; var sut = new SonarCompilationStartAnalysisContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context); - sut.Tree.Should().Be(tree); - sut.Compilation.Should().Be(model.Compilation); - sut.Options.Should().Be(options); + sut.Tree.Should().BeSameAs(tree); + sut.Compilation.Should().BeSameAs(model.Compilation); + sut.Options.Should().BeSameAs(options); sut.Cancel.Should().Be(cancel); } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSymbolReportingContextTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSymbolReportingContextTest.cs index 69b18737bd6..5de2d2c8a0d 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSymbolReportingContextTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSymbolReportingContextTest.cs @@ -37,10 +37,10 @@ public void Properties_ArePropagated() var context = new SymbolAnalysisContext(symbol, model.Compilation, options, _ => { }, _ => true, cancel); var sut = new SonarSymbolReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context); - sut.Tree.Should().Be(tree); - sut.Compilation.Should().Be(model.Compilation); - sut.Options.Should().Be(options); + sut.Tree.Should().BeSameAs(tree); + sut.Compilation.Should().BeSameAs(model.Compilation); + sut.Options.Should().BeSameAs(options); sut.Cancel.Should().Be(cancel); - sut.Symbol.Should().Be(symbol); + sut.Symbol.Should().BeSameAs(symbol); } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSyntaxNodeReportingContextTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSyntaxNodeReportingContextTest.cs index 56a84db92fd..46afbe39457 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSyntaxNodeReportingContextTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSyntaxNodeReportingContextTest.cs @@ -37,13 +37,13 @@ public void Properties_ArePropagated() var context = new SyntaxNodeAnalysisContext(node, containingSymbol, model, options, _ => { }, _ => true, cancel); var sut = new SonarSyntaxNodeReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context); - sut.Tree.Should().Be(tree); - sut.Compilation.Should().Be(model.Compilation); - sut.Options.Should().Be(options); + sut.Tree.Should().BeSameAs(tree); + sut.Compilation.Should().BeSameAs(model.Compilation); + sut.Options.Should().BeSameAs(options); sut.Cancel.Should().Be(cancel); - sut.Node.Should().Be(node); - sut.SemanticModel.Should().Be(model); - sut.ContainingSymbol.Should().Be(containingSymbol); + sut.Node.Should().BeSameAs(node); + sut.SemanticModel.Should().BeSameAs(model); + sut.ContainingSymbol.Should().BeSameAs(containingSymbol); } #if NET // .NET Fx shows the message box directly, the exception cannot be caught diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSyntaxTreeReportingContextTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSyntaxTreeReportingContextTest.cs index 86dfbaac437..1ceab7b3cb7 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSyntaxTreeReportingContextTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSyntaxTreeReportingContextTest.cs @@ -45,9 +45,9 @@ public void Properties_ArePropagated() var context = new SyntaxTreeAnalysisContext(tree, options, _ => { }, _ => true, cancel); var sut = new SonarSyntaxTreeReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context, model.Compilation); - sut.Tree.Should().Be(tree); - sut.Compilation.Should().Be(model.Compilation); - sut.Options.Should().Be(options); + sut.Tree.Should().BeSameAs(tree); + sut.Compilation.Should().BeSameAs(model.Compilation); + sut.Options.Should().BeSameAs(options); sut.Cancel.Should().Be(cancel); } } From 2b2fec455add5737972aa004065851c5cdd3241f Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Mon, 23 Jan 2023 20:46:22 +0100 Subject: [PATCH 13/32] Add SonarSematicModelReportingContextTest --- .../SonarSematicModelReportingContextTest.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSematicModelReportingContextTest.cs diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSematicModelReportingContextTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSematicModelReportingContextTest.cs new file mode 100644 index 00000000000..a890dfa2084 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSematicModelReportingContextTest.cs @@ -0,0 +1,43 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2022 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 SonarAnalyzer.AnalysisContext; + +namespace SonarAnalyzer.UnitTest.AnalysisContext; + +[TestClass] +public class SonarSematicModelReportingContextTest +{ + [TestMethod] + public void Properties_ArePropagated() + { + var cancel = new CancellationToken(true); + var (tree, model) = TestHelper.CompileCS("// Nothing to see here"); + var options = AnalysisScaffolding.CreateOptions(); + var context = new SemanticModelAnalysisContext(model, options, _ => { }, _ => true, cancel); + var sut = new SonarSematicModelReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context); + + sut.Tree.Should().BeSameAs(tree); + sut.Compilation.Should().BeSameAs(model.Compilation); + sut.SemanticModel.Should().BeSameAs(model); + sut.Options.Should().BeSameAs(options); + sut.Cancel.Should().Be(cancel); + } +} From b19e0279c90a1a43239f44fc3b09704ded9d3ec6 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Tue, 24 Jan 2023 09:39:30 +0100 Subject: [PATCH 14/32] Revert SonarAnalysisContextBase --- .../SonarAnalysisContextBase.cs | 57 +++++++++---------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs index 41a463221f6..b0ef9dc8e76 100644 --- a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs @@ -24,22 +24,43 @@ namespace SonarAnalyzer.AnalysisContext; -public abstract class SonarAnalysisContextBase +public class SonarAnalysisContextBase { 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 SonarAnalysisContextBase(SonarAnalysisContext analysisContext) + 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 +{ + public abstract SyntaxTree Tree { get; } + public abstract Compilation Compilation { get; } + public abstract AnalyzerOptions Options { get; } + public abstract CancellationToken Cancel { get; } + + public SonarAnalysisContext AnalysisContext { get; } + public TContext Context { get; } + + protected SonarAnalysisContextBase(SonarAnalysisContext analysisContext, TContext context) { AnalysisContext = analysisContext ?? throw new ArgumentNullException(nameof(analysisContext)); + Context = context; } - public SonarAnalysisContext AnalysisContext { get; } - - public abstract AnalyzerOptions Options { get; } - public abstract Compilation Compilation { get; } + /// 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)); /// /// Reads configuration from SonarProjectConfig.xml file and caches the result for scope of this analysis. @@ -68,30 +89,6 @@ public bool IsTestProject() : projectType == ProjectType.Test; // Scanner >= 5.1 does authoritative decision that we follow } - 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 -{ - public abstract SyntaxTree Tree { get; } - public abstract CancellationToken Cancel { get; } - public TContext Context { get; } - - protected SonarAnalysisContextBase(SonarAnalysisContext analysisContext, TContext context) : base(analysisContext) - { - Context = context; - } - - /// 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)); - public bool IsUnchanged(SyntaxTree tree) => UnchangedFilesCache.GetValue(Compilation, _ => CreateUnchangedFilesHashSet()).Contains(tree.FilePath); From ea335f58db2c6e1169eb12185dcd34776cd15a8d Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Tue, 24 Jan 2023 10:35:02 +0100 Subject: [PATCH 15/32] Fix ReadParameters calcluation --- .../Rules/Utilities/UtilityAnalyzerBase.cs | 17 ++++++++--------- .../Rules/Utilities/UtilityAnalyzerBaseTest.cs | 17 ++++------------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index 84be151350f..31310d93298 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -50,22 +50,21 @@ internal static TextRange GetTextRange(FileLinePositionSpan lineSpan) => EndOffset = lineSpan.EndLinePosition.Character }; - protected void ReadParameters(SonarAnalysisContextBase c) + protected void ReadParameters(AnalyzerOptions options, string outPath, string language, bool isTestProject) { - var settings = c.Options.ParseSonarLintXmlSettings(); - var outPath = c.ProjectConfiguration().OutPath; + var settings = options.ParseSonarLintXmlSettings(); // For backward compatibility with S4MSB <= 5.0 - if (outPath == null && c.Options.ProjectOutFolderPath() is { } projectOutFolderAdditionalFile) + if (outPath == null && options.ProjectOutFolderPath() is { } projectOutFolderAdditionalFile) { outPath = projectOutFolderAdditionalFile.GetText().ToString().TrimEnd(); } if (settings.Any() && !string.IsNullOrEmpty(outPath)) { - IgnoreHeaderComments = PropertiesHelper.ReadIgnoreHeaderCommentsProperty(settings, c.Compilation.Language); - AnalyzeGeneratedCode = PropertiesHelper.ReadAnalyzeGeneratedCodeProperty(settings, c.Compilation.Language); - OutPath = Path.Combine(outPath, c.Compilation.Language == LanguageNames.CSharp ? "output-cs" : "output-vbnet"); + IgnoreHeaderComments = PropertiesHelper.ReadIgnoreHeaderCommentsProperty(settings, language); + AnalyzeGeneratedCode = PropertiesHelper.ReadAnalyzeGeneratedCodeProperty(settings, language); + OutPath = Path.Combine(outPath, language == LanguageNames.CSharp ? "output-cs" : "output-vbnet"); IsAnalyzerEnabled = true; - IsTestProject = c.IsTestProject(); + IsTestProject = isTestProject; } } } @@ -89,7 +88,7 @@ protected UtilityAnalyzerBase(string diagnosticId, string title) : base(diagnost protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(startContext => { - ReadParameters(startContext); + ReadParameters(startContext.Options, startContext.ProjectConfiguration().OutPath, startContext.Compilation.Language, startContext.IsTestProject()); if (!IsAnalyzerEnabled) { return; diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs index 2f038138c00..d8e93812ec6 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs @@ -23,6 +23,7 @@ using Microsoft.CodeAnalysis.VisualBasic; using SonarAnalyzer.AnalysisContext; using SonarAnalyzer.Common; +using SonarAnalyzer.Extensions; using SonarAnalyzer.Rules; namespace SonarAnalyzer.UnitTest.Rules.Utilities @@ -134,24 +135,14 @@ public TestUtilityAnalyzer(string language, params string[] additionalPaths) : b LanguageNames.VisualBasic => VisualBasicCompilation.Create(null), _ => throw new InvalidOperationException($"Unexpected {nameof(language)}: {language}") }; - ReadParameters(new SonarAnalysisTestContext(AnalysisScaffolding.CreateSonarAnalysisContext(), new AnalyzerOptions(additionalFiles), compilation)); + var analyzerOptions = new AnalyzerOptions(additionalFiles); + var config = analyzerOptions.SonarProjectConfig() is { } sonarProjectConfig ? new ProjectConfigReader(sonarProjectConfig.GetText()) : ProjectConfigReader.Empty; + ReadParameters(analyzerOptions, config.OutPath, language, isTestProject: false); } protected override void Initialize(SonarAnalysisContext context) => throw new NotImplementedException(); - private class SonarAnalysisTestContext : SonarAnalysisContextBase - { - public SonarAnalysisTestContext(SonarAnalysisContext context, AnalyzerOptions options, Compilation compilation) : base(context) - { - Options = options; - Compilation = compilation; - } - - public override AnalyzerOptions Options { get; } - - public override Compilation Compilation { get; } - } } } } From 530385b52301c8846b59bbc276b51aefd8d295cd Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Tue, 24 Jan 2023 11:02:12 +0100 Subject: [PATCH 16/32] ReadParameters more scaffolding --- .../Rules/Utilities/UtilityAnalyzerBaseTest.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs index d8e93812ec6..8383a1b2a8c 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs @@ -137,12 +137,17 @@ public TestUtilityAnalyzer(string language, params string[] additionalPaths) : b }; var analyzerOptions = new AnalyzerOptions(additionalFiles); var config = analyzerOptions.SonarProjectConfig() is { } sonarProjectConfig ? new ProjectConfigReader(sonarProjectConfig.GetText()) : ProjectConfigReader.Empty; - ReadParameters(analyzerOptions, config.OutPath, language, isTestProject: false); + var isTestProject = config.ProjectType switch + { + ProjectType.Unknown => compilation.IsTest(), + ProjectType.Test => true, + _ => false + }; + ReadParameters(analyzerOptions, config.OutPath, language, isTestProject); } protected override void Initialize(SonarAnalysisContext context) => - throw new NotImplementedException(); - + throw new NotSupportedException(); } } } From 5efaaa87614bb11ce931901f0fd92fb3edf745e5 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Tue, 24 Jan 2023 11:10:35 +0100 Subject: [PATCH 17/32] Revert header change. --- .../AnalysisContext/SonarAnalysisContextBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs index b0ef9dc8e76..cfdbab112e5 100644 --- a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs @@ -1,6 +1,6 @@ /* * SonarAnalyzer for .NET - * Copyright (C) 2015-2023 SonarSource SA + * Copyright (C) 2015-2022 SonarSource SA * mailto: contact AT sonarsource DOT com * * This program is free software; you can redistribute it and/or From de401c8a9c4a81c845a1c49b31fbe850c5fa67f5 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Tue, 24 Jan 2023 11:23:34 +0100 Subject: [PATCH 18/32] Update header --- .../AnalysisContext/SonarSematicModelReportingContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarSematicModelReportingContext.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarSematicModelReportingContext.cs index 6cfcc85043b..594b7141faf 100644 --- a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarSematicModelReportingContext.cs +++ b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarSematicModelReportingContext.cs @@ -1,6 +1,6 @@ /* * SonarAnalyzer for .NET - * Copyright (C) 2015-2022 SonarSource SA + * Copyright (C) 2015-2023 SonarSource SA * mailto: contact AT sonarsource DOT com * * This program is free software; you can redistribute it and/or From 233ccaba561a45f14a399d9d72b35506b7786bdd Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 25 Jan 2023 17:24:43 +0100 Subject: [PATCH 19/32] Use SonarCompilationStartAnalysisContext for ReadParameters --- .../Rules/Utilities/UtilityAnalyzerBase.cs | 12 +++++++----- .../Rules/Utilities/UtilityAnalyzerBaseTest.cs | 12 +++--------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index 31310d93298..81bfd006fc4 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -50,21 +50,23 @@ internal static TextRange GetTextRange(FileLinePositionSpan lineSpan) => EndOffset = lineSpan.EndLinePosition.Character }; - protected void ReadParameters(AnalyzerOptions options, string outPath, string language, bool isTestProject) + protected void ReadParameters(SonarCompilationStartAnalysisContext context) { - var settings = options.ParseSonarLintXmlSettings(); + var settings = context.Options.ParseSonarLintXmlSettings(); + var outPath = context.ProjectConfiguration().OutPath; // For backward compatibility with S4MSB <= 5.0 - if (outPath == null && options.ProjectOutFolderPath() is { } projectOutFolderAdditionalFile) + if (outPath == null && context.Options.ProjectOutFolderPath() is { } projectOutFolderAdditionalFile) { outPath = projectOutFolderAdditionalFile.GetText().ToString().TrimEnd(); } if (settings.Any() && !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"); IsAnalyzerEnabled = true; - IsTestProject = isTestProject; + IsTestProject = context.IsTestProject(); } } } @@ -88,7 +90,7 @@ protected UtilityAnalyzerBase(string diagnosticId, string title) : base(diagnost protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(startContext => { - ReadParameters(startContext.Options, startContext.ProjectConfiguration().OutPath, startContext.Compilation.Language, startContext.IsTestProject()); + ReadParameters(startContext); if (!IsAnalyzerEnabled) { return; diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs index 8383a1b2a8c..8f03a14688f 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/Utilities/UtilityAnalyzerBaseTest.cs @@ -21,6 +21,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.VisualBasic; +using Moq; using SonarAnalyzer.AnalysisContext; using SonarAnalyzer.Common; using SonarAnalyzer.Extensions; @@ -135,15 +136,8 @@ public TestUtilityAnalyzer(string language, params string[] additionalPaths) : b LanguageNames.VisualBasic => VisualBasicCompilation.Create(null), _ => throw new InvalidOperationException($"Unexpected {nameof(language)}: {language}") }; - var analyzerOptions = new AnalyzerOptions(additionalFiles); - var config = analyzerOptions.SonarProjectConfig() is { } sonarProjectConfig ? new ProjectConfigReader(sonarProjectConfig.GetText()) : ProjectConfigReader.Empty; - var isTestProject = config.ProjectType switch - { - ProjectType.Unknown => compilation.IsTest(), - ProjectType.Test => true, - _ => false - }; - ReadParameters(analyzerOptions, config.OutPath, language, isTestProject); + var context = new Mock(compilation, new AnalyzerOptions(additionalFiles), default).Object; + ReadParameters(new SonarCompilationStartAnalysisContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context)); } protected override void Initialize(SonarAnalysisContext context) => From c4b9e0fc7dd98b94120f12e571c2aeec3eaa3f94 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 25 Jan 2023 17:30:54 +0100 Subject: [PATCH 20/32] Use List instead of Stack for collecting. --- .../Rules/Utilities/UtilityAnalyzerBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index 81bfd006fc4..9bc2768de09 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -96,13 +96,13 @@ protected sealed override void Initialize(SonarAnalysisContext context) => return; } - Stack treeMessages = new(); + List treeMessages = new(); startContext.RegisterSemanticModelAction(modelContext => { if (ShouldGenerateMetrics(modelContext)) { - treeMessages.Push(CreateMessage(modelContext.Tree, modelContext.SemanticModel)); + treeMessages.Add(CreateMessage(modelContext.Tree, modelContext.SemanticModel)); } }); From 0c129ae5dbe6ac20d9e6300156b8816ce9ee70ec Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 25 Jan 2023 17:45:39 +0100 Subject: [PATCH 21/32] Use base class for ShouldGenerateMetrics --- .../Rules/Utilities/UtilityAnalyzerBase.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index 9bc2768de09..f45c55727cf 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -97,7 +97,6 @@ protected sealed override void Initialize(SonarAnalysisContext context) => } List treeMessages = new(); - startContext.RegisterSemanticModelAction(modelContext => { if (ShouldGenerateMetrics(modelContext)) @@ -130,7 +129,7 @@ protected virtual bool ShouldGenerateMetrics(SyntaxTree tree) => && FileExtensionWhitelist.Contains(Path.GetExtension(tree.FilePath)) && (AnalyzeGeneratedCode || !Language.GeneratedCodeRecognizer.IsGenerated(tree)); - private bool ShouldGenerateMetrics(SonarSematicModelReportingContext context) => + private bool ShouldGenerateMetrics(SonarAnalysisContextBase context) => (AnalyzeUnchangedFiles || !context.IsUnchanged(context.Tree)) && ShouldGenerateMetrics(context.Tree); } From 1ef09f22ad3e56cf220f3aab7d8502977ac62651 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 25 Jan 2023 17:45:59 +0100 Subject: [PATCH 22/32] Formatting --- .../SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index f45c55727cf..accf42b8c0f 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -96,7 +96,7 @@ protected sealed override void Initialize(SonarAnalysisContext context) => return; } - List treeMessages = new(); + var treeMessages = new List(); startContext.RegisterSemanticModelAction(modelContext => { if (ShouldGenerateMetrics(modelContext)) From 14768917b97e08fe18f8594daec8a778ac9ac1aa Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 25 Jan 2023 18:07:28 +0100 Subject: [PATCH 23/32] Allow ReadParameters to be called from more registrations. --- .../SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index accf42b8c0f..e111cac20ea 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -50,7 +50,7 @@ internal static TextRange GetTextRange(FileLinePositionSpan lineSpan) => EndOffset = lineSpan.EndLinePosition.Character }; - protected void ReadParameters(SonarCompilationStartAnalysisContext context) + protected void ReadParameters(SonarAnalysisContextBase context) { var settings = context.Options.ParseSonarLintXmlSettings(); var outPath = context.ProjectConfiguration().OutPath; From b6f90a8672db0a304defbb069383cb9f3f4fd6a2 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Mon, 30 Jan 2023 11:44:55 +0100 Subject: [PATCH 24/32] Add tests for Registrations in SonarCompilationStartAnalysisContext --- ...lationStartAnalysisContextTest.Register.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCompilationStartAnalysisContextTest.Register.cs diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCompilationStartAnalysisContextTest.Register.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCompilationStartAnalysisContextTest.Register.cs new file mode 100644 index 00000000000..bfe206e2437 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCompilationStartAnalysisContextTest.Register.cs @@ -0,0 +1,64 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2022 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.Linq.Expressions; +using Moq; +using SonarAnalyzer.AnalysisContext; + +namespace SonarAnalyzer.UnitTest.AnalysisContext +{ + public partial class SonarAnalysisContextTest + { + [TestMethod] + public void SonarCompilationStartAnalysisContext_RegisterCompilationEndAction() => + TestStartContextRegistration( + registrationSetup: x => x.RegisterCompilationEndAction(It.IsAny>()), + registration: (context, action) => context.RegisterCompilationEndAction(action)); + + [TestMethod] + public void SonarCompilationStartAnalysisContext_RegisterSemanticModel() + { + TestStartContextRegistration( + registrationSetup: x => x.RegisterSemanticModelAction(It.IsAny>()), + registration: (context, action) => context.RegisterSemanticModelAction(action)); + } + + [TestMethod] + public void SonarCompilationStartAnalysisContext_RegisterSymbolAction() => + TestStartContextRegistration( + registrationSetup: x => x.RegisterSymbolAction(It.IsAny>(), It.IsAny>()), + registration: (context, action) => context.RegisterSymbolAction(action)); + + public void TestStartContextRegistration(Expression> registrationSetup, + Action> registration) + { + var context = new DummyAnalysisContext(TestContext); + var roslynStartContextMock = new Mock(context.Model.Compilation, context.Options, CancellationToken.None); + roslynStartContextMock.Setup(registrationSetup).Callback(new InvocationAction(x => + (x.Arguments[0] as Delegate).DynamicInvoke(new object[] { null }))); + var startContext = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), roslynStartContextMock.Object); + var wasExecuted = 0; + registration(startContext, x => wasExecuted++); + wasExecuted.Should().Be(1); + roslynStartContextMock.Verify(registrationSetup, Times.Once); + roslynStartContextMock.VerifyNoOtherCalls(); + } + } +} From d957c2d693cb1435dcb9d30f8a4acf395159fb68 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 1 Feb 2023 18:26:58 +0100 Subject: [PATCH 25/32] Use SonarSematicModelReportingContext as parameter type and pass tree explicit. --- .../Rules/Utilities/UtilityAnalyzerBase.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index e111cac20ea..534a382e7f0 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -99,7 +99,7 @@ protected sealed override void Initialize(SonarAnalysisContext context) => var treeMessages = new List(); startContext.RegisterSemanticModelAction(modelContext => { - if (ShouldGenerateMetrics(modelContext)) + if (ShouldGenerateMetrics(modelContext, modelContext.Tree)) { treeMessages.Add(CreateMessage(modelContext.Tree, modelContext.SemanticModel)); } @@ -129,8 +129,8 @@ protected virtual bool ShouldGenerateMetrics(SyntaxTree tree) => && FileExtensionWhitelist.Contains(Path.GetExtension(tree.FilePath)) && (AnalyzeGeneratedCode || !Language.GeneratedCodeRecognizer.IsGenerated(tree)); - private bool ShouldGenerateMetrics(SonarAnalysisContextBase context) => - (AnalyzeUnchangedFiles || !context.IsUnchanged(context.Tree)) - && ShouldGenerateMetrics(context.Tree); + private bool ShouldGenerateMetrics(SonarSematicModelReportingContext context, SyntaxTree tree) => + (AnalyzeUnchangedFiles || !context.IsUnchanged(tree)) + && ShouldGenerateMetrics(tree); } } From 9ac6322234baf7fae3a68b7b32e7593357707369 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 1 Feb 2023 18:27:31 +0100 Subject: [PATCH 26/32] Move tests to SonarAnalysisContextTest.Register.cs --- .../SonarAnalysisContextTest.Register.cs | 36 +++++++++++ ...lationStartAnalysisContextTest.Register.cs | 64 ------------------- 2 files changed, 36 insertions(+), 64 deletions(-) delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCompilationStartAnalysisContextTest.Register.cs diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs index 19640c54cfb..ad0e4378f47 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs @@ -21,6 +21,7 @@ extern alias csharp; extern alias vbnet; +using System.Linq.Expressions; using Microsoft.CodeAnalysis.CSharp; using Moq; using SonarAnalyzer.AnalysisContext; @@ -132,6 +133,41 @@ public void RegisterCodeBlockStartAction_UnchangedFiles_SonarAnalysisContext(str context.AssertDelegateInvoked(expected); } + [TestMethod] + public void SonarCompilationStartAnalysisContext_RegisterCompilationEndAction() => + TestStartContextRegistration( + registrationSetup: x => x.RegisterCompilationEndAction(It.IsAny>()), + registration: (context, action) => context.RegisterCompilationEndAction(action)); + + [TestMethod] + public void SonarCompilationStartAnalysisContext_RegisterSemanticModel() + { + TestStartContextRegistration( + registrationSetup: x => x.RegisterSemanticModelAction(It.IsAny>()), + registration: (context, action) => context.RegisterSemanticModelAction(action)); + } + + [TestMethod] + public void SonarCompilationStartAnalysisContext_RegisterSymbolAction() => + TestStartContextRegistration( + registrationSetup: x => x.RegisterSymbolAction(It.IsAny>(), It.IsAny>()), + registration: (context, action) => context.RegisterSymbolAction(action)); + + public void TestStartContextRegistration(Expression> registrationSetup, + Action> registration) + { + var context = new DummyAnalysisContext(TestContext); + var roslynStartContextMock = new Mock(context.Model.Compilation, context.Options, CancellationToken.None); + roslynStartContextMock.Setup(registrationSetup).Callback(new InvocationAction(x => + (x.Arguments[0] as Delegate).DynamicInvoke(new object[] { null }))); + var startContext = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), roslynStartContextMock.Object); + var wasExecuted = 0; + registration(startContext, x => wasExecuted++); + wasExecuted.Should().Be(1); + roslynStartContextMock.Verify(registrationSetup, Times.Once); + roslynStartContextMock.VerifyNoOtherCalls(); + } + private static CompilationStartAnalysisContext MockCompilationStartAnalysisContext(DummyAnalysisContext context) { var mock = new Mock(context.Model.Compilation, context.Options, CancellationToken.None); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCompilationStartAnalysisContextTest.Register.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCompilationStartAnalysisContextTest.Register.cs deleted file mode 100644 index bfe206e2437..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarCompilationStartAnalysisContextTest.Register.cs +++ /dev/null @@ -1,64 +0,0 @@ -/* - * SonarAnalyzer for .NET - * Copyright (C) 2015-2022 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.Linq.Expressions; -using Moq; -using SonarAnalyzer.AnalysisContext; - -namespace SonarAnalyzer.UnitTest.AnalysisContext -{ - public partial class SonarAnalysisContextTest - { - [TestMethod] - public void SonarCompilationStartAnalysisContext_RegisterCompilationEndAction() => - TestStartContextRegistration( - registrationSetup: x => x.RegisterCompilationEndAction(It.IsAny>()), - registration: (context, action) => context.RegisterCompilationEndAction(action)); - - [TestMethod] - public void SonarCompilationStartAnalysisContext_RegisterSemanticModel() - { - TestStartContextRegistration( - registrationSetup: x => x.RegisterSemanticModelAction(It.IsAny>()), - registration: (context, action) => context.RegisterSemanticModelAction(action)); - } - - [TestMethod] - public void SonarCompilationStartAnalysisContext_RegisterSymbolAction() => - TestStartContextRegistration( - registrationSetup: x => x.RegisterSymbolAction(It.IsAny>(), It.IsAny>()), - registration: (context, action) => context.RegisterSymbolAction(action)); - - public void TestStartContextRegistration(Expression> registrationSetup, - Action> registration) - { - var context = new DummyAnalysisContext(TestContext); - var roslynStartContextMock = new Mock(context.Model.Compilation, context.Options, CancellationToken.None); - roslynStartContextMock.Setup(registrationSetup).Callback(new InvocationAction(x => - (x.Arguments[0] as Delegate).DynamicInvoke(new object[] { null }))); - var startContext = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), roslynStartContextMock.Object); - var wasExecuted = 0; - registration(startContext, x => wasExecuted++); - wasExecuted.Should().Be(1); - roslynStartContextMock.Verify(registrationSetup, Times.Once); - roslynStartContextMock.VerifyNoOtherCalls(); - } - } -} From dacee9babb649ec505b086c03ed43ad55c955259 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 1 Feb 2023 18:42:37 +0100 Subject: [PATCH 27/32] Adopt tests to use a stub instead of the mock. --- .../SonarAnalysisContextTest.Register.cs | 81 +++++++++++++------ 1 file changed, 57 insertions(+), 24 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs index ad0e4378f47..1a6c008371a 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs @@ -20,8 +20,6 @@ extern alias csharp; extern alias vbnet; - -using System.Linq.Expressions; using Microsoft.CodeAnalysis.CSharp; using Moq; using SonarAnalyzer.AnalysisContext; @@ -134,38 +132,47 @@ public void RegisterCodeBlockStartAction_UnchangedFiles_SonarAnalysisContext(str } [TestMethod] - public void SonarCompilationStartAnalysisContext_RegisterCompilationEndAction() => - TestStartContextRegistration( - registrationSetup: x => x.RegisterCompilationEndAction(It.IsAny>()), - registration: (context, action) => context.RegisterCompilationEndAction(action)); + public void SonarCompilationStartAnalysisContext_RegisterCompilationEndAction() + { + var context = new DummyAnalysisContext(TestContext); + var startContext = new DummyCompilationStartAnalysisContext(context); + var sut = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), startContext); + sut.RegisterCompilationEndAction(_ => { }); + + startContext.AssertExpectedInvocationCounts(expectedCompilationEndCount: 1); + } [TestMethod] public void SonarCompilationStartAnalysisContext_RegisterSemanticModel() { - TestStartContextRegistration( - registrationSetup: x => x.RegisterSemanticModelAction(It.IsAny>()), - registration: (context, action) => context.RegisterSemanticModelAction(action)); + var context = new DummyAnalysisContext(TestContext); + var startContext = new DummyCompilationStartAnalysisContext(context); + var sut = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), startContext); + sut.RegisterSemanticModelAction(_ => { }); + + startContext.AssertExpectedInvocationCounts(expectedSemanticModelCount: 1); } [TestMethod] - public void SonarCompilationStartAnalysisContext_RegisterSymbolAction() => - TestStartContextRegistration( - registrationSetup: x => x.RegisterSymbolAction(It.IsAny>(), It.IsAny>()), - registration: (context, action) => context.RegisterSymbolAction(action)); + public void SonarCompilationStartAnalysisContext_RegisterSymbolAction() + { + var context = new DummyAnalysisContext(TestContext); + var startContext = new DummyCompilationStartAnalysisContext(context); + var sut = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), startContext); + sut.RegisterSymbolAction(_ => { }); + + startContext.AssertExpectedInvocationCounts(expectedSymbolCount: 1); + } - public void TestStartContextRegistration(Expression> registrationSetup, - Action> registration) + [TestMethod] + public void SonarCompilationStartAnalysisContext_RegisterNodeAction() { var context = new DummyAnalysisContext(TestContext); - var roslynStartContextMock = new Mock(context.Model.Compilation, context.Options, CancellationToken.None); - roslynStartContextMock.Setup(registrationSetup).Callback(new InvocationAction(x => - (x.Arguments[0] as Delegate).DynamicInvoke(new object[] { null }))); - var startContext = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), roslynStartContextMock.Object); - var wasExecuted = 0; - registration(startContext, x => wasExecuted++); - wasExecuted.Should().Be(1); - roslynStartContextMock.Verify(registrationSetup, Times.Once); - roslynStartContextMock.VerifyNoOtherCalls(); + var startContext = new DummyCompilationStartAnalysisContext(context); + var sut = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), startContext); + sut.RegisterNodeAction(CSharpGeneratedCodeRecognizer.Instance, _ => { }); + + startContext.AssertExpectedInvocationCounts(expectedNodeCount: 0); // RegisterNodeAction doesn't use DummyCompilationStartAnalysisContext to register but a newly created context } private static CompilationStartAnalysisContext MockCompilationStartAnalysisContext(DummyAnalysisContext context) @@ -236,6 +243,32 @@ public override void RegisterSyntaxNodeAction(Action throw new NotImplementedException(); } + private class DummyCompilationStartAnalysisContext : CompilationStartAnalysisContext + { + private int compilationEndCount; + private int semanticModelCount; + private int symbolCount; + private int nodeCount; + + public DummyCompilationStartAnalysisContext(DummyAnalysisContext context) : base(context.Model.Compilation, context.Options, default) { } + + public void AssertExpectedInvocationCounts(int expectedCompilationEndCount = 0, int expectedSemanticModelCount = 0, int expectedSymbolCount = 0, int expectedNodeCount = 0) + { + compilationEndCount.Should().Be(expectedCompilationEndCount); + semanticModelCount.Should().Be(expectedSemanticModelCount); + symbolCount.Should().Be(expectedSymbolCount); + nodeCount.Should().Be(expectedNodeCount); + } + + public override void RegisterCodeBlockAction(Action action) => throw new NotImplementedException(); + public override void RegisterCodeBlockStartAction(Action> action) => throw new NotImplementedException(); + public override void RegisterCompilationEndAction(Action action) => compilationEndCount++; + public override void RegisterSemanticModelAction(Action action) => semanticModelCount++; + public override void RegisterSymbolAction(Action action, ImmutableArray symbolKinds) => symbolCount++; + public override void RegisterSyntaxNodeAction(Action action, ImmutableArray syntaxKinds) => nodeCount++; + public override void RegisterSyntaxTreeAction(Action action) => throw new NotImplementedException(); + } + [DiagnosticAnalyzer(LanguageNames.CSharp)] private class DummyAnalyzerForGenerated : SonarDiagnosticAnalyzer { From 1d7fcfc9540874ac3c1b6bad642e4fa1ca04b7f8 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 1 Feb 2023 18:45:39 +0100 Subject: [PATCH 28/32] Fix date in header --- .../AnalysisContext/SonarAnalysisContextBase.cs | 2 +- .../AnalysisContext/SonarSematicModelReportingContextTest.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs index cfdbab112e5..b0ef9dc8e76 100644 --- a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarAnalysisContextBase.cs @@ -1,6 +1,6 @@ /* * SonarAnalyzer for .NET - * Copyright (C) 2015-2022 SonarSource SA + * Copyright (C) 2015-2023 SonarSource SA * mailto: contact AT sonarsource DOT com * * This program is free software; you can redistribute it and/or diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSematicModelReportingContextTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSematicModelReportingContextTest.cs index a890dfa2084..a01a1ed8eb8 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSematicModelReportingContextTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarSematicModelReportingContextTest.cs @@ -1,6 +1,6 @@ /* * SonarAnalyzer for .NET - * Copyright (C) 2015-2022 SonarSource SA + * Copyright (C) 2015-2023 SonarSource SA * mailto: contact AT sonarsource DOT com * * This program is free software; you can redistribute it and/or From d2c4cc99d459a4bc1c54a66ce99ae1004e0359f1 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 2 Feb 2023 14:30:52 +0100 Subject: [PATCH 29/32] Change param type to more derived --- .../SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index 534a382e7f0..63b9813ee63 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -50,7 +50,7 @@ internal static TextRange GetTextRange(FileLinePositionSpan lineSpan) => EndOffset = lineSpan.EndLinePosition.Character }; - protected void ReadParameters(SonarAnalysisContextBase context) + protected void ReadParameters(SonarCompilationStartAnalysisContext context) { var settings = context.Options.ParseSonarLintXmlSettings(); var outPath = context.ProjectConfiguration().OutPath; From 0915e4751150d4706c7cff0afd2ba466d09a43ec Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 2 Feb 2023 16:37:16 +0100 Subject: [PATCH 30/32] Test issue reporting. --- .../SonarAnalysisContextTest.Register.cs | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs index 1a6c008371a..6f2dc7a8ba8 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs @@ -175,6 +175,18 @@ public void SonarCompilationStartAnalysisContext_RegisterNodeAction() startContext.AssertExpectedInvocationCounts(expectedNodeCount: 0); // RegisterNodeAction doesn't use DummyCompilationStartAnalysisContext to register but a newly created context } + [TestMethod] + public void SonarCompilationStartAnalysisContext_RegisterSemanticModel_ReportsIssue() + { + var context = new DummyAnalysisContext(TestContext); + var startContext = new DummyCompilationStartAnalysisContext(context); + var sut = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), startContext); + var diagnostic = Diagnostic.Create(DiagnosticDescriptorFactory.CreateUtility("TEST", "Test report"), context.Tree.GetRoot().GetLocation()); + sut.RegisterSemanticModelAction(x => x.ReportIssue(CSharpGeneratedCodeRecognizer.Instance, diagnostic)); + + startContext.RaisedDiagnostic.Should().NotBeNull().And.BeSameAs(diagnostic); + } + private static CompilationStartAnalysisContext MockCompilationStartAnalysisContext(DummyAnalysisContext context) { var mock = new Mock(context.Model.Compilation, context.Options, CancellationToken.None); @@ -245,12 +257,18 @@ public override void RegisterSyntaxNodeAction(Action private class DummyCompilationStartAnalysisContext : CompilationStartAnalysisContext { + private readonly DummyAnalysisContext context; private int compilationEndCount; private int semanticModelCount; private int symbolCount; private int nodeCount; - public DummyCompilationStartAnalysisContext(DummyAnalysisContext context) : base(context.Model.Compilation, context.Options, default) { } + public Diagnostic RaisedDiagnostic { get; private set; } + + public DummyCompilationStartAnalysisContext(DummyAnalysisContext context) : base(context.Model.Compilation, context.Options, default) + { + this.context = context; + } public void AssertExpectedInvocationCounts(int expectedCompilationEndCount = 0, int expectedSemanticModelCount = 0, int expectedSymbolCount = 0, int expectedNodeCount = 0) { @@ -263,7 +281,12 @@ public void AssertExpectedInvocationCounts(int expectedCompilationEndCount = 0, public override void RegisterCodeBlockAction(Action action) => throw new NotImplementedException(); public override void RegisterCodeBlockStartAction(Action> action) => throw new NotImplementedException(); public override void RegisterCompilationEndAction(Action action) => compilationEndCount++; - public override void RegisterSemanticModelAction(Action action) => semanticModelCount++; + public override void RegisterSemanticModelAction(Action action) + { + semanticModelCount++; + action(new SemanticModelAnalysisContext(context.Model, context.Options, reportDiagnostic: x => RaisedDiagnostic = x, isSupportedDiagnostic: _ => true, CancellationToken.None)); + } + public override void RegisterSymbolAction(Action action, ImmutableArray symbolKinds) => symbolCount++; public override void RegisterSyntaxNodeAction(Action action, ImmutableArray syntaxKinds) => nodeCount++; public override void RegisterSyntaxTreeAction(Action action) => throw new NotImplementedException(); From 8b91a49900075c975dc1410e056a33ca3f4450e8 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Mon, 6 Feb 2023 09:49:21 +0100 Subject: [PATCH 31/32] Add more reporting tests --- .../SonarAnalysisContextTest.Register.cs | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs index 6f2dc7a8ba8..999dfc223fb 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs @@ -187,6 +187,30 @@ public void SonarCompilationStartAnalysisContext_RegisterSemanticModel_ReportsIs startContext.RaisedDiagnostic.Should().NotBeNull().And.BeSameAs(diagnostic); } + [TestMethod] + public void SonarCompilationStartAnalysisContext_RegisterCompilationEnd_ReportsIssue() + { + var context = new DummyAnalysisContext(TestContext); + var startContext = new DummyCompilationStartAnalysisContext(context); + var sut = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), startContext); + var diagnostic = Diagnostic.Create(DiagnosticDescriptorFactory.CreateUtility("TEST", "Test report"), context.Tree.GetRoot().GetLocation()); + sut.RegisterCompilationEndAction(x => x.ReportIssue(CSharpGeneratedCodeRecognizer.Instance, diagnostic)); + + startContext.RaisedDiagnostic.Should().NotBeNull().And.BeSameAs(diagnostic); + } + + [TestMethod] + public void SonarCompilationStartAnalysisContext_RegistSymbol_ReportsIssue() + { + var context = new DummyAnalysisContext(TestContext); + var startContext = new DummyCompilationStartAnalysisContext(context); + var sut = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), startContext); + var diagnostic = Diagnostic.Create(DiagnosticDescriptorFactory.CreateUtility("TEST", "Test report"), context.Tree.GetRoot().GetLocation()); + sut.RegisterSymbolAction(x => x.ReportIssue(CSharpGeneratedCodeRecognizer.Instance, diagnostic)); + + startContext.RaisedDiagnostic.Should().NotBeNull().And.BeSameAs(diagnostic); + } + private static CompilationStartAnalysisContext MockCompilationStartAnalysisContext(DummyAnalysisContext context) { var mock = new Mock(context.Model.Compilation, context.Options, CancellationToken.None); @@ -280,14 +304,25 @@ public void AssertExpectedInvocationCounts(int expectedCompilationEndCount = 0, public override void RegisterCodeBlockAction(Action action) => throw new NotImplementedException(); public override void RegisterCodeBlockStartAction(Action> action) => throw new NotImplementedException(); - public override void RegisterCompilationEndAction(Action action) => compilationEndCount++; + public override void RegisterCompilationEndAction(Action action) + { + compilationEndCount++; + action(new CompilationAnalysisContext(context.Model.Compilation, context.Options, reportDiagnostic: x => RaisedDiagnostic = x, isSupportedDiagnostic: _ => true, CancellationToken.None)); + } + public override void RegisterSemanticModelAction(Action action) { semanticModelCount++; action(new SemanticModelAnalysisContext(context.Model, context.Options, reportDiagnostic: x => RaisedDiagnostic = x, isSupportedDiagnostic: _ => true, CancellationToken.None)); } - public override void RegisterSymbolAction(Action action, ImmutableArray symbolKinds) => symbolCount++; + public override void RegisterSymbolAction(Action action, ImmutableArray symbolKinds) + { + symbolCount++; + action(new SymbolAnalysisContext(Mock.Of(), context.Model.Compilation, context.Options, + reportDiagnostic: x => RaisedDiagnostic = x, isSupportedDiagnostic: _ => true, CancellationToken.None)); + } + public override void RegisterSyntaxNodeAction(Action action, ImmutableArray syntaxKinds) => nodeCount++; public override void RegisterSyntaxTreeAction(Action action) => throw new NotImplementedException(); } From ec2cddc01cf50389480d1e7b371e82bb196153b8 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Fri, 10 Feb 2023 12:54:48 +0100 Subject: [PATCH 32/32] PR feedback --- .../SonarSematicModelReportingContext.cs | 2 +- .../Rules/Utilities/UtilityAnalyzerBase.cs | 8 +++---- .../SonarAnalysisContextTest.Register.cs | 21 ++++++++++++------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarSematicModelReportingContext.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarSematicModelReportingContext.cs index 594b7141faf..7ce1c496f3b 100644 --- a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarSematicModelReportingContext.cs +++ b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarSematicModelReportingContext.cs @@ -20,7 +20,7 @@ namespace SonarAnalyzer.AnalysisContext; -public sealed class SonarSematicModelReportingContext : SonarCompilationReportingContextBase +public sealed class SonarSematicModelReportingContext : SonarTreeReportingContextBase { public override SyntaxTree Tree => SemanticModel.SyntaxTree; public override Compilation Compilation => Context.SemanticModel.Compilation; diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs index 63b9813ee63..9ccc48ce48f 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Utilities/UtilityAnalyzerBase.cs @@ -99,7 +99,7 @@ protected sealed override void Initialize(SonarAnalysisContext context) => var treeMessages = new List(); startContext.RegisterSemanticModelAction(modelContext => { - if (ShouldGenerateMetrics(modelContext, modelContext.Tree)) + if (ShouldGenerateMetrics(modelContext)) { treeMessages.Add(CreateMessage(modelContext.Tree, modelContext.SemanticModel)); } @@ -129,8 +129,8 @@ protected virtual bool ShouldGenerateMetrics(SyntaxTree tree) => && FileExtensionWhitelist.Contains(Path.GetExtension(tree.FilePath)) && (AnalyzeGeneratedCode || !Language.GeneratedCodeRecognizer.IsGenerated(tree)); - private bool ShouldGenerateMetrics(SonarSematicModelReportingContext context, SyntaxTree tree) => - (AnalyzeUnchangedFiles || !context.IsUnchanged(tree)) - && ShouldGenerateMetrics(tree); + private bool ShouldGenerateMetrics(SonarSematicModelReportingContext context) => + (AnalyzeUnchangedFiles || !context.IsUnchanged(context.Tree)) + && ShouldGenerateMetrics(context.Tree); } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs index 999dfc223fb..eb1261368fe 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/AnalysisContext/SonarAnalysisContextTest.Register.cs @@ -182,7 +182,7 @@ public void SonarCompilationStartAnalysisContext_RegisterSemanticModel_ReportsIs var startContext = new DummyCompilationStartAnalysisContext(context); var sut = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), startContext); var diagnostic = Diagnostic.Create(DiagnosticDescriptorFactory.CreateUtility("TEST", "Test report"), context.Tree.GetRoot().GetLocation()); - sut.RegisterSemanticModelAction(x => x.ReportIssue(CSharpGeneratedCodeRecognizer.Instance, diagnostic)); + sut.RegisterSemanticModelAction(x => x.ReportIssue(diagnostic)); startContext.RaisedDiagnostic.Should().NotBeNull().And.BeSameAs(diagnostic); } @@ -289,10 +289,8 @@ private class DummyCompilationStartAnalysisContext : CompilationStartAnalysisCon public Diagnostic RaisedDiagnostic { get; private set; } - public DummyCompilationStartAnalysisContext(DummyAnalysisContext context) : base(context.Model.Compilation, context.Options, default) - { + public DummyCompilationStartAnalysisContext(DummyAnalysisContext context) : base(context.Model.Compilation, context.Options, default) => this.context = context; - } public void AssertExpectedInvocationCounts(int expectedCompilationEndCount = 0, int expectedSemanticModelCount = 0, int expectedSymbolCount = 0, int expectedNodeCount = 0) { @@ -302,8 +300,12 @@ public void AssertExpectedInvocationCounts(int expectedCompilationEndCount = 0, nodeCount.Should().Be(expectedNodeCount); } - public override void RegisterCodeBlockAction(Action action) => throw new NotImplementedException(); - public override void RegisterCodeBlockStartAction(Action> action) => throw new NotImplementedException(); + public override void RegisterCodeBlockAction(Action action) => + throw new NotImplementedException(); + + public override void RegisterCodeBlockStartAction(Action> action) => + throw new NotImplementedException(); + public override void RegisterCompilationEndAction(Action action) { compilationEndCount++; @@ -323,8 +325,11 @@ public override void RegisterSymbolAction(Action action, reportDiagnostic: x => RaisedDiagnostic = x, isSupportedDiagnostic: _ => true, CancellationToken.None)); } - public override void RegisterSyntaxNodeAction(Action action, ImmutableArray syntaxKinds) => nodeCount++; - public override void RegisterSyntaxTreeAction(Action action) => throw new NotImplementedException(); + public override void RegisterSyntaxNodeAction(Action action, ImmutableArray syntaxKinds) => + nodeCount++; + + public override void RegisterSyntaxTreeAction(Action action) => + throw new NotImplementedException(); } [DiagnosticAnalyzer(LanguageNames.CSharp)]