Skip to content

Commit

Permalink
Make UtilityAnalyzerBase stateless by removing properties (#6902)
Browse files Browse the repository at this point in the history
  • Loading branch information
martin-strecker-sonarsource committed Oct 18, 2023
1 parent 5bf048e commit 1a326f9
Show file tree
Hide file tree
Showing 16 changed files with 168 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ protected AnalysisWarningAnalyzerBase() : base(DiagnosticId, Title) { }
protected sealed override void Initialize(SonarAnalysisContext context) =>
context.RegisterCompilationAction(c =>
{
ReadParameters(c);
if (IsAnalyzerEnabled && !RoslynHelper.IsRoslynCfgSupported(MinimalSupportedRoslynVersion)) // MsBuild 15 is bound with Roslyn 2.x, where Roslyn CFG is not available.
var parameter = ReadParameters(c);
if (parameter.IsAnalyzerEnabled && !RoslynHelper.IsRoslynCfgSupported(MinimalSupportedRoslynVersion)) // MsBuild 15 is bound with Roslyn 2.x, where Roslyn CFG is not available.
{
// This can be removed after we bump Microsoft.CodeAnalysis references to 3.0 or higher.
var path = Path.GetFullPath(Path.Combine(OutPath, "../../AnalysisWarnings.MsBuild.json"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ public abstract class CopyPasteTokenAnalyzerBase<TSyntaxKind> : UtilityAnalyzerB
protected abstract string GetCpdValue(SyntaxToken token);
protected abstract bool IsUsingDirective(SyntaxNode node);

protected sealed override bool AnalyzeTestProjects => false;
protected override UtilityAnalyzerParameters ReadParameters(SonarCompilationStartAnalysisContext context) =>
base.ReadParameters(context) with { AnalyzeTestProjects = false };

protected sealed override bool AnalyzeUnchangedFiles => true;
protected sealed override string FileName => "token-cpd.pb";

Expand All @@ -41,7 +43,7 @@ protected override bool ShouldGenerateMetrics(SyntaxTree tree, Compilation compi
!GeneratedCodeRecognizer.IsRazorGeneratedFile(tree)
&& base.ShouldGenerateMetrics(tree, compilation);

protected sealed override CopyPasteTokenInfo CreateMessage(SyntaxTree tree, SemanticModel model)
protected sealed override CopyPasteTokenInfo CreateMessage(UtilityAnalyzerParameters parameters, SyntaxTree tree, SemanticModel model)
{
var cpdTokenInfo = new CopyPasteTokenInfo { FilePath = tree.FilePath };
foreach (var token in tree.GetRoot().DescendantTokens(n => !IsUsingDirective(n)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ public abstract class FileMetadataAnalyzerBase<TSyntaxKind> : UtilityAnalyzerBas
private const string Title = "File metadata generator";

protected sealed override string FileName => "file-metadata.pb";
protected override bool AnalyzeGeneratedCode => true;
protected override UtilityAnalyzerParameters ReadParameters(SonarCompilationStartAnalysisContext context) =>
base.ReadParameters(context) with { AnalyzeGeneratedCode = true };

protected FileMetadataAnalyzerBase() : base(DiagnosticId, Title) { }

protected override bool ShouldGenerateMetrics(SyntaxTree tree, Compilation compilation) =>
protected override bool ShouldGenerateMetrics(UtilityAnalyzerParameters parameters, SyntaxTree tree, Compilation compilation) =>
!GeneratedCodeRecognizer.IsRazorGeneratedFile(tree)
&& base.ShouldGenerateMetrics(tree, compilation);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public abstract class LogAnalyzerBase<TSyntaxKind> : UtilityAnalyzerBase<TSyntax
protected abstract string LanguageVersion(Compilation compilation);

protected sealed override string FileName => "log.pb";
protected override bool AnalyzeGeneratedCode => true;
protected override UtilityAnalyzerParameters ReadParameters(SonarCompilationStartAnalysisContext context) =>
base.ReadParameters(context) with { AnalyzeGeneratedCode = true };

protected LogAnalyzerBase() : base(DiagnosticId, Title) { }

Expand All @@ -43,7 +44,7 @@ protected sealed override IEnumerable<LogInfo> CreateAnalysisMessages(SonarCompi
new LogInfo { Severity = LogSeverity.Info, Text = "Concurrent execution: " + (IsConcurrentExecutionEnabled() ? "enabled" : "disabled") }
};

protected sealed override LogInfo CreateMessage(SyntaxTree tree, SemanticModel model) =>
protected sealed override LogInfo CreateMessage(UtilityAnalyzerParameters parameters, SyntaxTree tree, SemanticModel model) =>
tree.IsGenerated(Language.GeneratedCodeRecognizer, model.Compilation)
? CreateMessage(tree)
: null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ public abstract class MetricsAnalyzerBase<TSyntaxKind> : UtilityAnalyzerBase<TSy
protected abstract MetricsBase GetMetrics(SyntaxTree syntaxTree, SemanticModel semanticModel);

protected sealed override string FileName => "metrics.pb";
protected sealed override bool AnalyzeTestProjects => false;
protected override UtilityAnalyzerParameters ReadParameters(SonarCompilationStartAnalysisContext context) =>
base.ReadParameters(context) with { AnalyzeTestProjects = false };

protected MetricsAnalyzerBase() : base(DiagnosticId, Title) { }

protected sealed override MetricsInfo CreateMessage(SyntaxTree tree, SemanticModel model)
protected sealed override MetricsInfo CreateMessage(UtilityAnalyzerParameters parameters, SyntaxTree tree, SemanticModel model)
{
var metrics = GetMetrics(tree, model);
var complexity = metrics.Complexity;
Expand All @@ -49,7 +50,7 @@ protected sealed override MetricsInfo CreateMessage(SyntaxTree tree, SemanticMod
CognitiveComplexity = metrics.CognitiveComplexity,
};

var comments = metrics.GetComments(IgnoreHeaderComments);
var comments = metrics.GetComments(parameters.IgnoreHeaderComments);
metricsInfo.NoSonarComment.AddRange(comments.NoSonar);
metricsInfo.NonBlankComment.AddRange(comments.NonBlank);
metricsInfo.CodeLine.AddRange(metrics.CodeLines);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public abstract class SymbolReferenceAnalyzerBase<TSyntaxKind> : UtilityAnalyzer

protected SymbolReferenceAnalyzerBase() : base(DiagnosticId, Title) { }

protected sealed override SymbolReferenceInfo CreateMessage(SyntaxTree tree, SemanticModel model)
protected sealed override SymbolReferenceInfo CreateMessage(UtilityAnalyzerParameters parameters, SyntaxTree tree, SemanticModel model)
{
var filePath = GetFilePath(tree);
var symbolReferenceInfo = new SymbolReferenceInfo { FilePath = filePath };
Expand All @@ -54,7 +54,7 @@ protected sealed override SymbolReferenceInfo CreateMessage(SyntaxTree tree, Sem
return symbolReferenceInfo;
}

protected override bool ShouldGenerateMetrics(SyntaxTree tree, Compilation compilation) =>
protected override bool ShouldGenerateMetrics(UtilityAnalyzerParameters parameters, SyntaxTree tree, Compilation compilation) =>
base.ShouldGenerateMetrics(tree, compilation)
&& !HasTooManyTokens(tree);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ protected override bool ShouldGenerateMetrics(SyntaxTree tree, Compilation compi
!GeneratedCodeRecognizer.IsRazorGeneratedFile(tree)
&& base.ShouldGenerateMetrics(tree, compilation);

protected sealed override TokenTypeInfo CreateMessage(SyntaxTree tree, SemanticModel model)
protected sealed override TokenTypeInfo CreateMessage(UtilityAnalyzerParameters parameters, SyntaxTree tree, SemanticModel model)
{
var tokens = tree.GetRoot().DescendantTokens();
var identifierTokenKind = Language.SyntaxKind.IdentifierToken; // Performance optimization
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,21 @@

namespace SonarAnalyzer.Rules
{
public readonly record struct UtilityAnalyzerParameters(bool IsAnalyzerEnabled, bool IgnoreHeaderComments, bool AnalyzeGeneratedCode, bool AnalyzeTestProjects, string OutPath, bool IsTestProject)
{
public static readonly UtilityAnalyzerParameters Default =
new(IsAnalyzerEnabled: false, IgnoreHeaderComments: false, AnalyzeGeneratedCode: false, AnalyzeTestProjects: true, OutPath: null, IsTestProject: false);
}

public abstract class UtilityAnalyzerBase : SonarDiagnosticAnalyzer
{
protected static readonly ISet<string> FileExtensionWhitelist = new HashSet<string> { ".cs", ".csx", ".vb" };
private readonly DiagnosticDescriptor rule;

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(rule);
protected bool IsAnalyzerEnabled { get; set; }
protected bool IgnoreHeaderComments { get; set; }
protected virtual bool AnalyzeGeneratedCode { get; set; }
protected virtual bool AnalyzeTestProjects => true;
protected string OutPath { get; set; }
protected bool IsTestProject { get; set; }
protected override bool EnableConcurrentExecution => false;

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(rule);

protected UtilityAnalyzerBase(string diagnosticId, string title) =>
rule = DiagnosticDescriptorFactory.CreateUtility(diagnosticId, title);

Expand All @@ -50,7 +51,7 @@ internal static TextRange GetTextRange(FileLinePositionSpan lineSpan) =>
EndOffset = lineSpan.EndLinePosition.Character
};

protected void ReadParameters(SonarCompilationReportingContext context)
protected virtual UtilityAnalyzerParameters ReadParameters(SonarCompilationStartAnalysisContext context)
{
var outPath = context.ProjectConfiguration().OutPath;
// For backward compatibility with S4MSB <= 5.0
Expand All @@ -60,13 +61,17 @@ protected void ReadParameters(SonarCompilationReportingContext context)
}
if (context.Options.SonarLintXml() != null && !string.IsNullOrEmpty(outPath))
{
var language = context.Compilation.Language;
var sonarLintXml = context.SonarLintXml();
IgnoreHeaderComments = sonarLintXml.IgnoreHeaderComments(context.Compilation.Language);
AnalyzeGeneratedCode = sonarLintXml.AnalyzeGeneratedCode(context.Compilation.Language);
OutPath = Path.Combine(outPath, context.Compilation.Language == LanguageNames.CSharp ? "output-cs" : "output-vbnet");
IsAnalyzerEnabled = true;
IsTestProject = context.IsTestProject();
return new UtilityAnalyzerParameters(
IsAnalyzerEnabled: true,
IgnoreHeaderComments: sonarLintXml.IgnoreHeaderComments(language),
AnalyzeGeneratedCode: sonarLintXml.AnalyzeGeneratedCode(language),
AnalyzeTestProjects: true,
OutPath: Path.Combine(outPath, language == LanguageNames.CSharp ? "output-cs" : "output-vbnet"),
IsTestProject: context.IsTestProject());
}
return UtilityAnalyzerParameters.Default;
}
}

Expand All @@ -77,25 +82,26 @@ public abstract class UtilityAnalyzerBase<TSyntaxKind, TMessage> : UtilityAnalyz
protected abstract ILanguageFacade<TSyntaxKind> Language { get; }
protected abstract string FileName { get; }
protected abstract TMessage CreateMessage(SyntaxTree tree, SemanticModel model);

protected virtual bool AnalyzeUnchangedFiles => false;

protected abstract TMessage CreateMessage(UtilityAnalyzerParameters parameters, SyntaxTree syntaxTree, SemanticModel semanticModel);

protected virtual IEnumerable<TMessage> CreateAnalysisMessages(SonarCompilationReportingContext c) => Enumerable.Empty<TMessage>();

protected UtilityAnalyzerBase(string diagnosticId, string title) : base(diagnosticId, title) { }

protected sealed override void Initialize(SonarAnalysisContext context) =>
context.RegisterCompilationAction(context =>
{
ReadParameters(context);
if (!IsAnalyzerEnabled)
var parameters = ReadParameters(startContext);
if (!parameters.IsAnalyzerEnabled)
{
return;
}

var treeMessages = context.Compilation.SyntaxTrees
.Where(x => ShouldGenerateMetrics(context, x))
.Select(x => CreateMessage(x, context.Compilation.GetSemanticModel(x)));
.Select(x => CreateMessage(parameters, x, context.Compilation.GetSemanticModel(x)));

var allMessages = CreateAnalysisMessages(context)
.Concat(treeMessages)
Expand All @@ -110,9 +116,9 @@ protected sealed override void Initialize(SonarAnalysisContext context) =>
}
});

protected virtual bool ShouldGenerateMetrics(SyntaxTree tree, Compilation compilation) =>
protected virtual bool ShouldGenerateMetrics(UtilityAnalyzerParameters parameters, SyntaxTree tree, Compilation compilation) =>
// 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)
(parameters.AnalyzeTestProjects || !parameters.IsTestProject)
&& FileExtensionWhitelist.Contains(Path.GetExtension(tree.FilePath))
&& ShouldGenerateMetricsByType(tree, compilation);

Expand All @@ -122,9 +128,9 @@ protected static string GetFilePath(SyntaxTree tree) =>
? root.GetMappedFilePathFromRoot()
: tree.FilePath;

private bool ShouldGenerateMetrics(SonarCompilationReportingContext context, SyntaxTree tree) =>
(AnalyzeUnchangedFiles || !context.IsUnchanged(tree))
&& ShouldGenerateMetrics(tree, context.Compilation);
private bool ShouldGenerateMetrics(UtilityAnalyzerParameters parameters, SonarSematicModelReportingContext context) =>
(AnalyzeUnchangedFiles || !context.IsUnchanged(context.Tree))
&& ShouldGenerateMetrics(parameters, context.Tree, context.Compilation);

private bool ShouldGenerateMetricsByType(SyntaxTree tree, Compilation compilation) =>
AnalyzeGeneratedCode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/

using System.IO;
using SonarAnalyzer.AnalysisContext;
using SonarAnalyzer.CFG.Helpers;
using SonarAnalyzer.Rules;
using CS = SonarAnalyzer.Rules.CSharp;
Expand Down Expand Up @@ -87,25 +88,35 @@ private string ExecuteAnalyzer(string languageName, bool isAnalyzerEnabled, int

private sealed class TestAnalysisWarningAnalyzer_CS : CS.AnalysisWarningAnalyzer
{
private readonly bool isAnalyzerEnabled;
private readonly string outPath;
protected override int MinimalSupportedRoslynVersion { get; }

public TestAnalysisWarningAnalyzer_CS(bool isAnalyzerEnabled, int minimalSupportedRoslynVersion, string outPath)
{
IsAnalyzerEnabled = isAnalyzerEnabled;
this.isAnalyzerEnabled = isAnalyzerEnabled;
MinimalSupportedRoslynVersion = minimalSupportedRoslynVersion;
OutPath = outPath;
this.outPath = outPath;
}

protected override UtilityAnalyzerParameters ReadParameters(SonarCompilationStartAnalysisContext context) =>
base.ReadParameters(context) with { IsAnalyzerEnabled = isAnalyzerEnabled, OutPath = outPath };
}

private sealed class TestAnalysisWarningAnalyzer_VB : VB.AnalysisWarningAnalyzer
{
private readonly bool isAnalyzerEnabled;
private readonly string outPath;
protected override int MinimalSupportedRoslynVersion { get; }

public TestAnalysisWarningAnalyzer_VB(bool isAnalyzerEnabled, int minimalSupportedRoslynVersion, string outPath)
{
IsAnalyzerEnabled = isAnalyzerEnabled;
this.isAnalyzerEnabled = isAnalyzerEnabled;
MinimalSupportedRoslynVersion = minimalSupportedRoslynVersion;
OutPath = outPath;
this.outPath = outPath;
}

protected override UtilityAnalyzerParameters ReadParameters(SonarCompilationStartAnalysisContext context) =>
base.ReadParameters(context) with { IsAnalyzerEnabled = isAnalyzerEnabled, OutPath = outPath };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
*/

using System.IO;
using SonarAnalyzer.AnalysisContext;
using SonarAnalyzer.Common;
using SonarAnalyzer.Protobuf;
using SonarAnalyzer.Rules;
using CS = SonarAnalyzer.Rules.CSharp;
Expand Down Expand Up @@ -162,22 +164,32 @@ private VerifierBuilder CreateBuilder(ProjectType projectType, string fileName)
// We need to set protected properties and this class exists just to enable the analyzer without bothering with additional files with parameters
private sealed class TestCopyPasteTokenAnalyzer_CS : CS.CopyPasteTokenAnalyzer
{
private readonly string outPath;
private readonly bool isTestProject;

public TestCopyPasteTokenAnalyzer_CS(string outPath, bool isTestProject)
{
IsAnalyzerEnabled = true;
OutPath = outPath;
IsTestProject = isTestProject;
this.outPath = outPath;
this.isTestProject = isTestProject;
}

protected override UtilityAnalyzerParameters ReadParameters(SonarCompilationStartAnalysisContext context) =>
base.ReadParameters(context) with { IsAnalyzerEnabled = true, OutPath = outPath, IsTestProject = isTestProject };
}

private sealed class TestCopyPasteTokenAnalyzer_VB : VB.CopyPasteTokenAnalyzer
{
private readonly string outPath;
private readonly bool isTestProject;

public TestCopyPasteTokenAnalyzer_VB(string outPath, bool isTestProject)
{
IsAnalyzerEnabled = true;
OutPath = outPath;
IsTestProject = isTestProject;
this.outPath = outPath;
this.isTestProject = isTestProject;
}

protected override UtilityAnalyzerParameters ReadParameters(SonarCompilationStartAnalysisContext context) =>
base.ReadParameters(context) with { IsAnalyzerEnabled = true, OutPath = outPath, IsTestProject = isTestProject };
}
}
}
Loading

0 comments on commit 1a326f9

Please sign in to comment.