Skip to content

Commit

Permalink
Add basic parallel.foreach to speed up processing documents in Roslyn…
Browse files Browse the repository at this point in the history
… Conventions (#96)
  • Loading branch information
paulegradie committed May 24, 2024
1 parent f704f87 commit 458b234
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,31 @@ public abstract class ConventionalSyntaxNodeAnalyzer : DiagnosticAnalyzer
{
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.IfStatement, SyntaxKind.ElseClause);
context.RegisterSyntaxNodeAction(Analyze, SyntaxKinds());
context.EnableConcurrentExecution();

// Generate diagnostic reports, but do not allow analyzer actions
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.ReportDiagnostics);
}

void Analyze(SyntaxNodeAnalysisContext context)
private void Analyze(SyntaxNodeAnalysisContext context)
{
var result = CheckNode(context.Node, context.SemanticModel);

if (result.Success == false)
if (result.Success)
{
var loc = context.Node.GetLocation();
var diagnostic = Diagnostic.Create(Rule, loc, $"{result.Message} must have braces");
context.ReportDiagnostic(diagnostic);
return;
}
var loc = context.Node.GetLocation();
var diagnostic = Diagnostic.Create(Rule, loc, $"{result.Message} must have braces");
context.ReportDiagnostic(diagnostic);
}

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

protected abstract DiagnosticDescriptor Rule { get; }

public abstract DiagnosticResult CheckNode(SyntaxNode node, SemanticModel semanticModel);
public abstract SyntaxKind[] SyntaxKinds();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,7 @@ public override DiagnosticResult CheckNode(SyntaxNode node, SemanticModel semant
return DiagnosticResult.Succeeded();
}
}

public override SyntaxKind[] SyntaxKinds() => new[] { SyntaxKind.IfStatement, SyntaxKind.ElseClause };
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Linq;
using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

Expand Down Expand Up @@ -28,5 +30,7 @@ public override DiagnosticResult CheckNode(SyntaxNode node, SemanticModel semant

return DiagnosticResult.Succeeded();
}

public override SyntaxKind[] SyntaxKinds() => Array.Empty<SyntaxKind>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ public void IfAndElseMustHaveBracesAnalyzer_Success()
}
}


[Test]
public void IfAndElseMustHaveBracesAnalyzer_FailsWhenIfBlockDoesNotHaveBrace()
{
Expand Down Expand Up @@ -88,7 +87,7 @@ public void UsingStatementsMustNotBeNestedAnalyzer_Success()
}

[Test]
public void UsingStatementsMustNotBeNestedAnalyzer_FailesWhenFileHasUsingsInsideNamespace()
public void UsingStatementsMustNotBeNestedAnalyzer_FailsWhenFileHasUsingsInsideNamespace()
{
using (new TestSolution("TestSolution"))
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
using System.Linq;
using Conventional.Roslyn.Analyzers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Conventional.Roslyn.Conventions
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class IfAndElseMustHaveBracesConventionSpecification : SolutionDiagnosticAnalyzerConventionSpecification
{
private readonly IfAndElseMustHaveBracesAnalyzer _analyzer;
protected override string FailureMessage => "If and else must have braces, and {0} statement on line {1} does not";

public IfAndElseMustHaveBracesConventionSpecification(string[] fileExemptions) : base(fileExemptions)
public IfAndElseMustHaveBracesConventionSpecification(string[] fileExemptions) : base(new IfAndElseMustHaveBracesAnalyzer(), fileExemptions)
{
_analyzer = new IfAndElseMustHaveBracesAnalyzer();
}

protected override DiagnosticResult CheckNode(SyntaxNode node, Document document = null, SemanticModel semanticModel = null)
{
var result = _analyzer.CheckNode(node, semanticModel);
var result = Analyzer.CheckNode(node, semanticModel);

if (result.Success == false)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
using System.Collections.Generic;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Conventional.Roslyn.Analyzers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace Conventional.Roslyn.Conventions
{
Expand All @@ -10,26 +14,41 @@ public interface ISolutionDiagnosticAnalyzerConventionSpecification
IEnumerable<ConventionResult> IsSatisfiedBy(Solution solution);
}

public abstract class SolutionDiagnosticAnalyzerConventionSpecification :
ISolutionDiagnosticAnalyzerConventionSpecification
public abstract class SolutionDiagnosticAnalyzerConventionSpecification : ISolutionDiagnosticAnalyzerConventionSpecification
{
// ReSharper disable once UnusedMemberInSuper.Global
protected abstract string FailureMessage { get; }
protected readonly ConventionalSyntaxNodeAnalyzer Analyzer;
private readonly string[] _fileExemptions;

protected SolutionDiagnosticAnalyzerConventionSpecification(string[] fileExemptions)
protected SolutionDiagnosticAnalyzerConventionSpecification(ConventionalSyntaxNodeAnalyzer diagnosticAnalyzer, string[] fileExemptions)
{
Analyzer = diagnosticAnalyzer;
_fileExemptions = fileExemptions;
}

public IEnumerable<ConventionResult> IsSatisfiedBy(Solution solution)
{
return solution.Projects.SelectMany(x => x.Documents
.Where(d => d.SupportsSyntaxTree)
.Where(d => !_fileExemptions.Any(d.FilePath.EndsWith)))
.SelectMany(IsSatisfiedBy);
var documents = solution
.Projects
.SelectMany(proj => proj.Documents
.Where(doc => doc.SupportsSyntaxTree)
.Where(FilterFileExceptions));

var concurrentBag = new ConcurrentBag<IEnumerable<ConventionResult>>();
Parallel.ForEach(
documents,
// the compiler thinks this is an analyzer
#pragma warning disable RS1035
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
#pragma warning restore RS1035
document => concurrentBag.Add(IsSatisfiedBy(document)));

return concurrentBag.SelectMany(x => x);
}

private bool FilterFileExceptions(Document document) => !_fileExemptions.Any(exemption => document.FilePath?.EndsWith(exemption) ?? true);

private IEnumerable<ConventionResult> IsSatisfiedBy(Document document)
{
var node = document.GetSyntaxRootAsync().Result;
Expand All @@ -56,6 +75,12 @@ private IEnumerable<ConventionResult> IsSatisfiedBy(Document document, SyntaxNod

private ConventionResult BuildResult(Document document, SyntaxNode node, SemanticModel semanticModel)
{
var kinds = Analyzer.SyntaxKinds();
if (kinds.Length > 0 && !kinds.Contains(node!.Kind()))
{
return ConventionResult.Satisfied(document.FilePath);
}

var result = CheckNode(node, document, semanticModel);

return result.Success
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
using System.Linq;
using Conventional.Roslyn.Analyzers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Conventional.Roslyn.Conventions
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class UsingsStatementsMustNotBeNestedConventionSpecification : SolutionDiagnosticAnalyzerConventionSpecification
{
private readonly UsingsStatementsMustNotBeNestedAnalyzer _analyzer;
protected override string FailureMessage => "{0} statements must not be nested within the namespace, using on line {1} does not conform";

public UsingsStatementsMustNotBeNestedConventionSpecification(string[] fileExemptions) : base(fileExemptions)
public UsingsStatementsMustNotBeNestedConventionSpecification(string[] fileExemptions) : base(new UsingsStatementsMustNotBeNestedAnalyzer(), fileExemptions)
{
_analyzer = new UsingsStatementsMustNotBeNestedAnalyzer();
}

protected override DiagnosticResult CheckNode(SyntaxNode node, Document document = null, SemanticModel semanticModel = null)
{
var result = _analyzer.CheckNode(node, semanticModel);
var result = Analyzer.CheckNode(node, semanticModel);

if (result.Success == false)
{
Expand Down

0 comments on commit 458b234

Please sign in to comment.