Skip to content

Adding Item 12 Analyzer, Provider and Tests #62

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

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c06ff97
started project. Must actually write analyzer
rorozcov Jul 30, 2024
55ca307
very rough draft. ugly code. need to test
rorozcov Jul 31, 2024
4dd9f2b
analyzer running correctly but off by one
rorozcov Aug 2, 2024
aaae648
works except that source file error ocurring
rorozcov Aug 2, 2024
74e8931
passed first test. Issue was in the analyzer instance being reused in…
rorozcov Aug 3, 2024
31cd29b
massive update. All tests passing except for initializing in declarat…
rorozcov Aug 4, 2024
8f352f7
analyzer passing all tests!
rorozcov Aug 5, 2024
cc88272
updated diagnostic ids. Implemented first part of code provider. Fixe…
rorozcov Aug 7, 2024
cba51c9
provider mostly working. Unexpected iterations issue + diagnostics ar…
rorozcov Aug 12, 2024
12662a8
fixed final bug. All tests pass. V1 is done! Time to optimize
rorozcov Aug 14, 2024
d886813
Added new edge case. Improved runtime and memory efficiency. Must cle…
rorozcov Aug 16, 2024
b07c606
Added untested benchmark
rorozcov Aug 17, 2024
b877bd3
cleaned up analyzer
rorozcov Aug 17, 2024
3761ec5
renaming files and classes
rorozcov Aug 20, 2024
52b2850
Update src/EffectiveCSharp.Analyzers/PreferDeclarationInitializersToA…
rorozcov Aug 21, 2024
58d7878
Update src/EffectiveCSharp.Analyzers/PreferDeclarationInitializersToA…
rorozcov Aug 21, 2024
b678fdb
removed IDictionary. Added back lines in tests.csproj
rorozcov Aug 21, 2024
ef99b90
finished a comment
rorozcov Aug 21, 2024
ceaa593
Add additional tests for corner cases
rjmurillo Aug 21, 2024
d2de257
Merge remote-tracking branch 'github-desktop-rorozcov/main' into pr/62
rjmurillo Aug 21, 2024
dde05f8
addressed comments and improved code fix provider
rorozcov Aug 22, 2024
d6a903b
Added logic to handle tests: FieldInitializedWithMethodDirectly and F…
rorozcov Aug 22, 2024
570bc93
handled all remaining tests except for one in question
rorozcov Aug 23, 2024
e3cdf69
Merge remote-tracking branch 'upstream/main'
rorozcov Aug 27, 2024
0b13830
changed ctor overload test to not expect diagnostic because it is a v…
rorozcov Aug 27, 2024
97c51a4
removed GetSymbolInfo calls since they were too expensive. Reduced ru…
rorozcov Aug 27, 2024
2c031a2
fixed all alerts except for boxing alerts
rorozcov Aug 28, 2024
6d7a03a
Testing better impl. WIP
rorozcov Aug 29, 2024
2636b70
improvements not ready
rorozcov Sep 3, 2024
c11f9cf
runtime much better. Missing memory improvements
rorozcov Sep 3, 2024
24d0ddd
addressed null-forgiving operator comments. Addressed issues flagged …
rorozcov Sep 5, 2024
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
7 changes: 4 additions & 3 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,18 @@
- https://github.com/dotnet/roslyn/blob/main/docs/wiki/NuGet-packages.md
- https://learn.microsoft.com/en-us/visualstudio/productinfo/vs-servicing
-->

<PackageVersion Include="BenchmarkDotNet.Diagnostics.dotMemory" Version="0.14.0" />
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These got inserted into the incorrect section

<PackageVersion Include="BenchmarkDotNet.Diagnostics.dotTrace" Version="0.14.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.3.1" />
</ItemGroup>
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why'd this get bumped?

<PackageVersion Include="GetPackFromProject" Version="1.0.6" />
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.6.139" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta1.21216.1" />
<PackageVersion Include="System.CommandLine.Rendering" Version="2.0.0-beta1.20074.1" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Perfolizer" Version="0.2.1" />
<PackageVersion Include="Perfolizer" Version="0.3.17" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.13" />
</ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
"msbuild-sdks": {
"DotNet.ReproducibleBuilds.Isolated": "1.2.4"
}
}
}
9 changes: 9 additions & 0 deletions src/EffectiveCSharp.Analyzers/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
; Unshipped analyzer release
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
ECS1200 | Maintainability | Info | PreferDeclarationInitializersToAssignmentStatement, [Documentation](https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/97c51a41b53d059cfcd06f0c8ce5ab178b070e6b/docs/rules/ECS1200.md)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are having these rules confusing to end users? They can't be distilled down into fewer (or one)?

ECS1201 | Maintainability | Info | PreferDeclarationInitializersExceptNullOrZero, [Documentation](https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/97c51a41b53d059cfcd06f0c8ce5ab178b070e6b/docs/rules/ECS1201.md)
ECS1202 | Maintainability | Info | PreferDeclarationInitializersExceptWhenVaryingInitializations, [Documentation](https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/97c51a41b53d059cfcd06f0c8ce5ab178b070e6b/docs/rules/ECS1202.md)
ECS1203 | Maintainability | Info | PreferDeclarationInitializersWhenNoInitializationPresent, [Documentation](https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/97c51a41b53d059cfcd06f0c8ce5ab178b070e6b/docs/rules/ECS1203.md)
4 changes: 4 additions & 0 deletions src/EffectiveCSharp.Analyzers/Common/DiagnosticIds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@ internal static class DiagnosticIds
internal const string MinimizeBoxingUnboxing = "ECS0900";
internal const string BeAwareOfValueTypeCopyInReferenceTypes = "ECS0901";
internal const string UseSpanInstead = "ECS1000";
internal const string PreferDeclarationInitializersToAssignmentStatement = "ECS1200";
internal const string PreferDeclarationInitializersExceptNullOrZero = "ECS1201";
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other cases this is covered by CA1805: Do not initialize unnecessarily. In those cases we want to instead update our global configuration to enable that analyzer. If there is a gap in that analyzer, then we should file an issue for triage and contribute a fix.

There is also some cases of this is covered in Roslynator RCS1129 (code, docs).

internal const string PreferDeclarationInitializersExceptWhenVaryingInitializations = "ECS1202";
internal const string PreferDeclarationInitializersWhenNoInitializationPresent = "ECS1203";
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
namespace EffectiveCSharp.Analyzers;

/// <summary>
/// A <see cref="CodeFixProvider"/> that provides a code fix for the <see cref="PreferDeclarationInitializersToAssignmentStatementsAnalyzer"/>.
/// </summary>
/// <seealso cref="Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider" />
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PreferDeclarationInitializersToAssignmentStatementsCodeFixProvider))]
[Shared]
public class PreferDeclarationInitializersToAssignmentStatementsCodeFixProvider : CodeFixProvider
{
private static readonly string EquivalenceKey = "ECS1200CodeFix";

/// <inheritdoc />
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
DiagnosticIds.PreferDeclarationInitializersToAssignmentStatement,
DiagnosticIds.PreferDeclarationInitializersExceptNullOrZero,
DiagnosticIds.PreferDeclarationInitializersExceptWhenVaryingInitializations,
DiagnosticIds.PreferDeclarationInitializersWhenNoInitializationPresent);

/// <inheritdoc />
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

/// <inheritdoc />
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
SemanticModel? semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);

if (root is null || semanticModel is null)
{
return;
}

foreach (Diagnostic diagnostic in context.Diagnostics)
{
TextSpan diagnosticSpan = diagnostic.Location.SourceSpan;

SyntaxNode diagnosticNode = root.FindNode(diagnosticSpan);

switch (diagnostic.Id)
{
case DiagnosticIds.PreferDeclarationInitializersToAssignmentStatement:
context.RegisterCodeFix(
CodeAction.Create(
title: "Initialize in field declaration instead",
createChangedSolution: cancellationToken => ReplaceAssignmentsWithFieldDeclarationInitializerAsync(context, semanticModel, diagnosticNode, cancellationToken),
equivalenceKey: EquivalenceKey),
context.Diagnostics);
break;
case DiagnosticIds.PreferDeclarationInitializersExceptNullOrZero:
context.RegisterCodeFix(
CodeAction.Create(
title: "Do not initialize to null or zero",
createChangedSolution: cancellationToken => EnforceFieldIsNotInitializedAsync(context.Document, diagnosticNode, cancellationToken),
equivalenceKey: EquivalenceKey),
diagnostic);
break;
case DiagnosticIds.PreferDeclarationInitializersExceptWhenVaryingInitializations:
context.RegisterCodeFix(
CodeAction.Create(
title: "Do not use field initializer when varying initializations",
createChangedSolution: cancellationToken => EnforceFieldIsNotInitializedAsync(context.Document, diagnosticNode, cancellationToken),
equivalenceKey: EquivalenceKey),
diagnostic);
break;
case DiagnosticIds.PreferDeclarationInitializersWhenNoInitializationPresent:
context.RegisterCodeFix(
CodeAction.Create(
title: "Use initializer in field declaration",
createChangedSolution: cancellationToken => EnforceFieldDeclarationInitializationAsync(context.Document, semanticModel, diagnosticNode, cancellationToken),
equivalenceKey: EquivalenceKey),
diagnostic);
break;
default:
continue;
}
}
}
Comment on lines +24 to +78
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main Async Registration Method

The RegisterCodeFixesAsync method is well-structured and handles different diagnostic IDs with appropriate code fixes. However, the method is quite lengthy and handles multiple responsibilities, which could be refactored into smaller, more manageable methods.

Consider refactoring each case in the switch statement into separate methods to improve readability and maintainability.


private static async Task<Solution> ReplaceAssignmentsWithFieldDeclarationInitializerAsync(CodeFixContext context, SemanticModel semanticModel, SyntaxNode diagnosticNode, CancellationToken cancellationToken)

Check failure on line 80 in src/EffectiveCSharp.Analyzers/PreferDeclarationInitializersToAssignmentStatementsCodeFixProvider.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/EffectiveCSharp.Analyzers/PreferDeclarationInitializersToAssignmentStatementsCodeFixProvider.cs#L80

The Cyclomatic Complexity of this method is 11 which is greater than 10 authorized.
{
SyntaxNode? root = await context.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

if (root is null

Check failure on line 84 in src/EffectiveCSharp.Analyzers/PreferDeclarationInitializersToAssignmentStatementsCodeFixProvider.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/EffectiveCSharp.Analyzers/PreferDeclarationInitializersToAssignmentStatementsCodeFixProvider.cs#L84

Reduce the number of conditional operators (7) used in the expression (maximum allowed 3).
|| diagnosticNode.Parent is not AssignmentExpressionSyntax assignmentExpression // The diagnostic should be an expression statement
|| assignmentExpression.Parent is not ExpressionStatementSyntax expressionStatementSyntax
|| semanticModel.GetSymbolInfo(assignmentExpression.Left, context.CancellationToken).Symbol is not IFieldSymbol fieldSymbol // The left side of the assignment should be a field
|| fieldSymbol.DeclaringSyntaxReferences.Length != 1 // The field should have a single declaration
|| fieldSymbol.DeclaringSyntaxReferences[0] is not { } syntaxReference // Let's get a reference to the field declaration
|| (await syntaxReference.GetSyntaxAsync(cancellationToken).ConfigureAwait(false)).Parent?.Parent is not FieldDeclarationSyntax existingFieldDeclaration // Let's get the field declaration
|| root.RemoveNode(expressionStatementSyntax, SyntaxRemoveOptions.KeepNoTrivia) is not { } newRoot // Let's remove the assignment statement since we've found the field and have an initializer
|| CreateFieldDeclaration(existingFieldDeclaration, GetInitializerFromExpressionSyntax(assignmentExpression.Right)) is not { } fieldDeclarationWithNewInitializer)
{
// If any of the above conditions are not met, return the current solution
return context.Document.Project.Solution;
}

// Replace the existing field declaration with the new field declaration
return context.Document.WithSyntaxRoot(newRoot.ReplaceNode(existingFieldDeclaration, fieldDeclarationWithNewInitializer)).Project.Solution;
}
Comment on lines +80 to +100
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Complex Conditional Logic

The method ReplaceAssignmentsWithFieldDeclarationInitializerAsync contains a complex chain of conditional checks. This complexity could lead to maintenance challenges and makes the code hard to read.

Consider simplifying the conditional logic or breaking down the method into smaller, more focused methods. Additionally, ensure that the comments are updated to reflect any changes in logic or method structure.


private static EqualsValueClauseSyntax GetInitializerFromExpressionSyntax(ExpressionSyntax assignmentExpression)
{
// We use a given expression syntax node, duplicate it, and create an EqualsValueClauseSyntax node from it
// which can serve as an initializer for a field declaration
return SyntaxFactory.EqualsValueClause(assignmentExpression.WithTriviaFrom(assignmentExpression));
}

private static async Task<Solution> EnforceFieldIsNotInitializedAsync(Document document, SyntaxNode declaration, CancellationToken cancellationToken)
{
SyntaxNode? root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

if (root is null
|| declaration is not FieldDeclarationSyntax fieldDeclaration // The declaration should be a field declaration
|| CreateFieldDeclaration(fieldDeclaration) is not { } newFieldDeclaration)
{
// If any of the above conditions are not met, return the current solution
return document.Project.Solution;
}

// Replace the existing field declaration with the new field declaration
return document.WithSyntaxRoot(root.ReplaceNode(fieldDeclaration, newFieldDeclaration)).Project.Solution;
}
Comment on lines +109 to +123
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Field Initialization Enforcement

The method EnforceFieldIsNotInitializedAsync is implemented correctly but could benefit from additional comments explaining the logic, especially around the conditions that lead to the early return of the current solution.

Adding more detailed comments would help other developers understand the decision points within this method.


private static async Task<Solution> EnforceFieldDeclarationInitializationAsync(Document document, SemanticModel semanticModel, SyntaxNode declaration, CancellationToken cancellationToken)
{
SyntaxNode? root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

if (root is null
|| declaration is not FieldDeclarationSyntax fieldDeclaration // The declaration should be a field declaration
|| fieldDeclaration.Declaration.Variables.Count != 1 // The field declaration should have a variable declarator
|| fieldDeclaration.Declaration.Variables[0] is not { } variableDeclarator // The field declaration should have a variable declarator
|| semanticModel.GetDeclaredSymbol(variableDeclarator, cancellationToken) is not IFieldSymbol fieldSymbol // Let's get the field symbol
|| GetDefaultInitializerForType(fieldSymbol.Type) is not { } newInitializer // Let's try to get an initializer for the field type
|| CreateFieldDeclaration(fieldDeclaration, newInitializer) is not { } fieldDeclarationWithInitializer)
{
// If any of the above conditions are not met, return the current solution
return document.Project.Solution;
}

// Replace the existing field declaration with the new field declaration
return document.WithSyntaxRoot(root.ReplaceNode(fieldDeclaration, fieldDeclarationWithInitializer)).Project.Solution;
}
Comment on lines +125 to +143
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default Initializer Handling

The method EnforceFieldDeclarationInitializationAsync handles the initialization of fields with default values. The method is complex and could be simplified. Additionally, the method assumes that there is only one variable declarator, which might not always be the case.

Refactor to handle multiple declarators and simplify the conditional logic to improve clarity and robustness.

Consider handling cases where multiple declarators are present in a field declaration.


private static EqualsValueClauseSyntax? GetDefaultInitializerForType(ITypeSymbol fieldType)
{
// If the field type is an interface or delegate, we do not want to initialize it
if (fieldType.TypeKind == TypeKind.Interface
|| fieldType.TypeKind == TypeKind.Delegate)
{
return null;
}

// If the field type is a string, we can initialize it to string.Empty
if (fieldType.SpecialType == SpecialType.System_String)
{
return SyntaxFactory.EqualsValueClause(SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.IdentifierName("string"), SyntaxFactory.IdentifierName("Empty")));
}

// If the field type is a generic type, we need to create a generic object creation expression
if (fieldType is INamedTypeSymbol { IsGenericType: true } namedTypeSymbol)
{
string genericTypeName = namedTypeSymbol.Name;
string genericArguments = string.Join(", ", namedTypeSymbol.TypeArguments.Select(arg => arg.ToDisplayString()));
string fullTypeName = $"{genericTypeName}<{genericArguments}>";
return SyntaxFactory.EqualsValueClause(SyntaxFactory.ObjectCreationExpression(SyntaxFactory.ParseTypeName(fullTypeName)).WithArgumentList(SyntaxFactory.ArgumentList()));
}

return SyntaxFactory.EqualsValueClause(SyntaxFactory.ObjectCreationExpression(SyntaxFactory.ParseTypeName(fieldType.Name)).WithArgumentList(SyntaxFactory.ArgumentList()));
}
Comment on lines +145 to +170
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type-Specific Initializers

The method GetDefaultInitializerForType is well-implemented with clear handling of different types. However, the method could be expanded to handle more types or made more generic to reduce the need for type-specific checks.

Consider using a strategy pattern or a dictionary of type handlers to simplify the method and make it easier to extend in the future.


private static FieldDeclarationSyntax? CreateFieldDeclaration(FieldDeclarationSyntax existingFieldDeclaration, EqualsValueClauseSyntax? newInitializer = null)
{
// Extract the existing variable declarator
VariableDeclaratorSyntax? existingVariableDeclarator = existingFieldDeclaration.Declaration.Variables.FirstOrDefault();

if (existingVariableDeclarator is null)
{
return null;
}

// Create a new variable declarator with the initializer
VariableDeclaratorSyntax newVariableDeclarator = SyntaxFactory.VariableDeclarator(existingVariableDeclarator.Identifier)
.WithInitializer(newInitializer);

// Create a new variable declaration with the new variable declarator
VariableDeclarationSyntax newVariableDeclaration = SyntaxFactory.VariableDeclaration(existingFieldDeclaration.Declaration.Type)
.WithVariables(SyntaxFactory.SingletonSeparatedList(newVariableDeclarator));

// Create a new field declaration with the new variable declaration and existing modifiers
return SyntaxFactory.FieldDeclaration(newVariableDeclaration)
.WithModifiers(existingFieldDeclaration.Modifiers);
}
Comment on lines +172 to +193
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Field Declaration Creation

The method CreateFieldDeclaration is crucial for the functionality of this code fix provider. It is implemented correctly but could be more robust. Specifically, it should handle cases where the existing field declaration might have multiple variables.

Refactor to support multiple variable declarators in the field declaration.

Consider refactoring to handle multiple variable declarators.

}
73 changes: 73 additions & 0 deletions tests/EffectiveCSharp.Analyzers.Benchmarks/Ecs1200Benchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using BenchmarkDotNet.Diagnostics.dotMemory;

Check failure on line 1 in tests/EffectiveCSharp.Analyzers.Benchmarks/Ecs1200Benchmarks.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/EffectiveCSharp.Analyzers.Benchmarks/Ecs1200Benchmarks.cs#L1

Provide an 'AssemblyVersion' attribute for assembly 'srcassembly.dll'.
using BenchmarkDotNet.Diagnostics.dotTrace;

namespace EffectiveCSharp.Analyzers.Benchmarks;

[InProcess]
[MemoryDiagnoser]
public class Ecs1200Benchmarks
{
private static CompilationWithAnalyzers? BaselineCompilation { get; set; }

private static CompilationWithAnalyzers? TestCompilation { get; set; }

[IterationSetup]
[SuppressMessage("Usage", "VSTHRD002:Avoid problematic synchronous waits", Justification = "Async setup not supported in BenchmarkDotNet.See https://github.com/dotnet/BenchmarkDotNet/issues/2442.")]
public static void SetupCompilation()
{
List<(string Name, string Content)> sources = [];
for (int index = 0; index < Constants.NumberOfCodeFiles; index++)
{
string name = "TypeName" + index;
sources.Add((name, @$"
internal class {name}
{{
private string labels;

public {name}()
{{
labels = string.Empty;
}}
}}
"));
}

(BaselineCompilation, TestCompilation) =
BenchmarkCSharpCompilationFactory
.CreateAsync<PreferDeclarationInitializersToAssignmentStatementsAnalyzer>(sources.ToArray())
.GetAwaiter()
.GetResult();
}

[Benchmark]
public async Task Ecs1200WithDiagnostics()
{
ImmutableArray<Diagnostic> diagnostics =
(await TestCompilation!
.GetAnalysisResultAsync(CancellationToken.None)
.ConfigureAwait(false))
.AssertValidAnalysisResult()
.GetAllDiagnostics();

if (diagnostics.Length != Constants.NumberOfCodeFiles)
{
throw new InvalidOperationException($"Expected '{Constants.NumberOfCodeFiles:N0}' analyzer diagnostics but found '{diagnostics.Length}'");
}
}

[Benchmark(Baseline = true)]
public async Task Ecs1200Baseline()
{
ImmutableArray<Diagnostic> diagnostics =
(await BaselineCompilation!
.GetAnalysisResultAsync(CancellationToken.None)
.ConfigureAwait(false))
.AssertValidAnalysisResult()
.GetAllDiagnostics();

if (diagnostics.Length != 0)
{
throw new InvalidOperationException($"Expected no analyzer diagnostics but found '{diagnostics.Length}'");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
<ItemGroup>
<ProjectReference Include="$(RepoRoot)/src/EffectiveCSharp.Analyzers/EffectiveCSharp.Analyzers.csproj" />
<PackageReference Include="BenchmarkDotNet" />
<PackageReference Include="BenchmarkDotNet.Diagnostics.dotMemory" />
<PackageReference Include="BenchmarkDotNet.Diagnostics.dotTrace" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing" />
</ItemGroup>
Expand Down
Loading
Loading