Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UtilityAnalyzer: Use RegisterCompilationStartAction #6576

Merged
merged 32 commits into from
Feb 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
490ee42
Use RegisterCompilationStartAction
martin-strecker-sonarsource Dec 20, 2022
3c81804
Remove redundant check
martin-strecker-sonarsource Dec 20, 2022
085fb99
Check for IsGenerated in test
martin-strecker-sonarsource Dec 20, 2022
f47bb6a
Use ConcurrentStack and don't take permanent dependency on startContext
martin-strecker-sonarsource Dec 20, 2022
76024bf
Use BeEquivalentTo instead of Contains/Count
martin-strecker-sonarsource Dec 20, 2022
e3e1754
Apply suggestions from code review
martin-strecker-sonarsource Jan 2, 2023
701acc3
Clean up after suggestions
martin-strecker-sonarsource Jan 2, 2023
0bd463e
Make it compile
martin-strecker-sonarsource Jan 17, 2023
a7aba58
Some clean-up.
martin-strecker-sonarsource Jan 17, 2023
c32cbd4
Fix call site
martin-strecker-sonarsource Jan 17, 2023
bd81944
Formatting and smaller changes
martin-strecker-sonarsource Jan 23, 2023
181fdf1
Fix test assertions
martin-strecker-sonarsource Jan 23, 2023
2b2fec4
Add SonarSematicModelReportingContextTest
martin-strecker-sonarsource Jan 23, 2023
b19e027
Revert SonarAnalysisContextBase
martin-strecker-sonarsource Jan 24, 2023
ea335f5
Fix ReadParameters calcluation
martin-strecker-sonarsource Jan 24, 2023
530385b
ReadParameters more scaffolding
martin-strecker-sonarsource Jan 24, 2023
5efaaa8
Revert header change.
martin-strecker-sonarsource Jan 24, 2023
de401c8
Update header
martin-strecker-sonarsource Jan 24, 2023
233ccab
Use SonarCompilationStartAnalysisContext for ReadParameters
martin-strecker-sonarsource Jan 25, 2023
c4b9e0f
Use List instead of Stack for collecting.
martin-strecker-sonarsource Jan 25, 2023
0c129ae
Use base class for ShouldGenerateMetrics
martin-strecker-sonarsource Jan 25, 2023
1ef09f2
Formatting
martin-strecker-sonarsource Jan 25, 2023
1476891
Allow ReadParameters to be called from more registrations.
martin-strecker-sonarsource Jan 25, 2023
b6f90a8
Add tests for Registrations in SonarCompilationStartAnalysisContext
martin-strecker-sonarsource Jan 30, 2023
d957c2d
Use SonarSematicModelReportingContext as parameter type and pass tree…
martin-strecker-sonarsource Feb 1, 2023
9ac6322
Move tests to SonarAnalysisContextTest.Register.cs
martin-strecker-sonarsource Feb 1, 2023
dacee9b
Adopt tests to use a stub instead of the mock.
martin-strecker-sonarsource Feb 1, 2023
1d7fcfc
Fix date in header
martin-strecker-sonarsource Feb 1, 2023
d2c4cc9
Change param type to more derived
martin-strecker-sonarsource Feb 2, 2023
0915e47
Test issue reporting.
martin-strecker-sonarsource Feb 2, 2023
8b91a49
Add more reporting tests
martin-strecker-sonarsource Feb 6, 2023
ec2cddc
PR feedback
martin-strecker-sonarsource Feb 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ internal SonarCompilationStartAnalysisContext(SonarAnalysisContext analysisConte
public void RegisterSymbolAction(Action<SonarSymbolReportingContext> action, params SymbolKind[] symbolKinds) =>
Context.RegisterSymbolAction(x => action(new(AnalysisContext, x)), symbolKinds);

public void RegisterSemanticModelAction(Action<SonarSematicModelReportingContext> action) =>
Context.RegisterSemanticModelAction(x => action(new(AnalysisContext, x)));
pavel-mikula-sonarsource marked this conversation as resolved.
Show resolved Hide resolved

public void RegisterCompilationEndAction(Action<SonarCompilationReportingContext> action) =>
Context.RegisterCompilationEndAction(x => action(new(AnalysisContext, x)));

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2023 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

namespace SonarAnalyzer.AnalysisContext;

public sealed class SonarSematicModelReportingContext : SonarTreeReportingContextBase<SemanticModelAnalysisContext>
{
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;

internal SonarSematicModelReportingContext(SonarAnalysisContext analysisContext, SemanticModelAnalysisContext context) : base(analysisContext, context) { }

private protected override ReportingContext CreateReportingContext(Diagnostic diagnostic) =>
new(this, diagnostic);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Diagnostic> roslynReportDiagnostic,
Compilation compilation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,23 @@ internal static TextRange GetTextRange(FileLinePositionSpan lineSpan) =>
EndOffset = lineSpan.EndLinePosition.Character
};

protected void ReadParameters(SonarCompilationReportingContext c)
protected void ReadParameters(SonarCompilationStartAnalysisContext context)
{
var settings = c.Options.ParseSonarLintXmlSettings();
var outPath = c.ProjectConfiguration().OutPath;
var settings = context.Options.ParseSonarLintXmlSettings();
var outPath = context.ProjectConfiguration().OutPath;
// For backward compatibility with S4MSB <= 5.0
if (outPath == null && c.Options.ProjectOutFolderPath() is { } projectOutFolderAdditionalFile)
if (outPath == null && context.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");
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 = c.IsTestProject();
IsTestProject = context.IsTestProject();
}
}
}
Expand All @@ -87,40 +88,49 @@ public abstract class UtilityAnalyzerBase<TSyntaxKind, TMessage> : UtilityAnalyz
protected UtilityAnalyzerBase(string diagnosticId, string title) : base(diagnosticId, title) { }

protected sealed override void Initialize(SonarAnalysisContext context) =>
context.RegisterCompilationAction(c =>
context.RegisterCompilationStartAction(startContext =>
{
ReadParameters(startContext);
if (!IsAnalyzerEnabled)
pavel-mikula-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
{
return;
}

var treeMessages = new List<TMessage>();
startContext.RegisterSemanticModelAction(modelContext =>
{
ReadParameters(c);
if (!IsAnalyzerEnabled)
if (ShouldGenerateMetrics(modelContext))
{
return;
treeMessages.Add(CreateMessage(modelContext.Tree, modelContext.SemanticModel));
}
});

var treeMessages = c.Compilation.SyntaxTrees
.Where(x => ShouldGenerateMetrics(c, x))
.Select(x => CreateMessage(x, c.Compilation.GetSemanticModel(x)));
var messages = CreateAnalysisMessages(c)
startContext.RegisterCompilationEndAction(endContext =>
{
var allMessages = CreateAnalysisMessages(endContext)
.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.
(AnalyzeTestProjects || !IsTestProject)
&& FileExtensionWhitelist.Contains(Path.GetExtension(tree.FilePath))
&& (AnalyzeGeneratedCode || !Language.GeneratedCodeRecognizer.IsGenerated(tree));

private bool ShouldGenerateMetrics(SonarCompilationReportingContext context, SyntaxTree tree) =>
(AnalyzeUnchangedFiles || !context.IsUnchanged(tree))
&& ShouldGenerateMetrics(tree);
private bool ShouldGenerateMetrics(SonarSematicModelReportingContext context) =>
(AnalyzeUnchangedFiles || !context.IsUnchanged(context.Tree))
&& ShouldGenerateMetrics(context.Tree);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

extern alias csharp;
extern alias vbnet;

using Microsoft.CodeAnalysis.CSharp;
using Moq;
using SonarAnalyzer.AnalysisContext;
Expand Down Expand Up @@ -132,6 +131,86 @@ public void RegisterCodeBlockStartAction_UnchangedFiles_SonarAnalysisContext(str
context.AssertDelegateInvoked(expected);
}

[TestMethod]
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()
{
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()
{
var context = new DummyAnalysisContext(TestContext);
var startContext = new DummyCompilationStartAnalysisContext(context);
var sut = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), startContext);
sut.RegisterSymbolAction(_ => { });

startContext.AssertExpectedInvocationCounts(expectedSymbolCount: 1);
pavel-mikula-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
}

[TestMethod]
public void SonarCompilationStartAnalysisContext_RegisterNodeAction()
{
var context = new DummyAnalysisContext(TestContext);
var startContext = new DummyCompilationStartAnalysisContext(context);
var sut = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), startContext);
sut.RegisterNodeAction<SyntaxKind>(CSharpGeneratedCodeRecognizer.Instance, _ => { });

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(diagnostic));

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<CompilationStartAnalysisContext>(context.Model.Compilation, context.Options, CancellationToken.None);
Expand Down Expand Up @@ -200,6 +279,59 @@ public override void RegisterSyntaxNodeAction(Action<SyntaxNodeAnalysisContext>
throw new NotImplementedException();
}

private class DummyCompilationStartAnalysisContext : CompilationStartAnalysisContext
{
private readonly DummyAnalysisContext context;
private int compilationEndCount;
private int semanticModelCount;
private int symbolCount;
private int nodeCount;

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)
{
compilationEndCount.Should().Be(expectedCompilationEndCount);
semanticModelCount.Should().Be(expectedSemanticModelCount);
symbolCount.Should().Be(expectedSymbolCount);
nodeCount.Should().Be(expectedNodeCount);
}

public override void RegisterCodeBlockAction(Action<CodeBlockAnalysisContext> action) =>
throw new NotImplementedException();

public override void RegisterCodeBlockStartAction<TLanguageKindEnum>(Action<CodeBlockStartAnalysisContext<TLanguageKindEnum>> action) =>
throw new NotImplementedException();

public override void RegisterCompilationEndAction(Action<CompilationAnalysisContext> action)
{
compilationEndCount++;
action(new CompilationAnalysisContext(context.Model.Compilation, context.Options, reportDiagnostic: x => RaisedDiagnostic = x, isSupportedDiagnostic: _ => true, CancellationToken.None));
}

public override void RegisterSemanticModelAction(Action<SemanticModelAnalysisContext> action)
{
semanticModelCount++;
action(new SemanticModelAnalysisContext(context.Model, context.Options, reportDiagnostic: x => RaisedDiagnostic = x, isSupportedDiagnostic: _ => true, CancellationToken.None));
}

public override void RegisterSymbolAction(Action<SymbolAnalysisContext> action, ImmutableArray<SymbolKind> symbolKinds)
{
symbolCount++;
action(new SymbolAnalysisContext(Mock.Of<ISymbol>(), context.Model.Compilation, context.Options,
reportDiagnostic: x => RaisedDiagnostic = x, isSupportedDiagnostic: _ => true, CancellationToken.None));
}

public override void RegisterSyntaxNodeAction<TLanguageKindEnum>(Action<SyntaxNodeAnalysisContext> action, ImmutableArray<TLanguageKindEnum> syntaxKinds) =>
nodeCount++;

public override void RegisterSyntaxTreeAction(Action<SyntaxTreeAnalysisContext> action) =>
throw new NotImplementedException();
}

[DiagnosticAnalyzer(LanguageNames.CSharp)]
private class DummyAnalyzerForGenerated : SonarDiagnosticAnalyzer
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ public void Properties_ArePropagated()
var context = new Mock<CodeBlockStartAnalysisContext<SyntaxKind>>(codeBlock, owningSymbol, model, options, cancel).Object;
var sut = new SonarCodeBlockStartAnalysisContext<SyntaxKind>(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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ public void Properties_ArePropagated()
var context = new Mock<CompilationStartAnalysisContext>(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);
}
}
Loading