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

Add fix all properties code fixes #25

Merged
merged 14 commits into from
Dec 16, 2024
116 changes: 73 additions & 43 deletions ReactiveGenerator.Tests/AnalyzerTestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,73 +5,101 @@
using Microsoft.CodeAnalysis.CodeActions;
using System.Runtime.CompilerServices;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System;
using System.Collections.Generic;

namespace ReactiveGenerator.Tests;

public static class AnalyzerTestHelper
{
public static async Task TestAndVerify(
public static Task TestAndVerify(
string source,
Dictionary<string, string>? analyzerConfigOptions = null,
[CallerMemberName] string? testName = null,
params DiagnosticAnalyzer[] analyzers)
{
return TestAndVerifyWithFix(source, null, analyzerConfigOptions, testName, analyzers);
}

public static async Task TestAndVerifyWithFix(
string source,
string? equivalenceKey,
Dictionary<string, string>? analyzerConfigOptions = null,
[CallerMemberName] string? testName = null,
params DiagnosticAnalyzer[] analyzers)
{
if (analyzers == null || analyzers.Length == 0)
{
throw new ArgumentException("At least one analyzer must be provided", nameof(analyzers));
}

// Create compilation
var compilation = CreateCompilation(source);

// Create analyzer driver
var compilationWithAnalyzers = compilation
.WithAnalyzers(ImmutableArray.Create(analyzers),
new AnalyzerOptions(ImmutableArray<AdditionalText>.Empty));
// Create initial project with the source
var project = CreateProject(source);
var document = project.Documents.First();

// Run analysis
var compilation = await project.GetCompilationAsync();
var compilationWithAnalyzers = compilation!
.WithAnalyzers(ImmutableArray.Create(analyzers),
new AnalyzerOptions(ImmutableArray<AdditionalText>.Empty));

// Get diagnostics synchronously to ensure consistency
var diagnostics = await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync();

// Apply code fixes if available
var codeFixProvider = new ReactivePropertyCodeFixProvider();
// Sort diagnostics deterministically
diagnostics = diagnostics.OrderBy(d => d.Location.SourceSpan.Start)
.ThenBy(d => d.Id)
.ToImmutableArray();

var newSource = source;

if (diagnostics.Any() && codeFixProvider != null)
if (diagnostics.Any())
{
var project = CreateProject(newSource);
var documentId = project.DocumentIds[0];
var document = project.GetDocument(documentId);

// Create a list to store all actions
var allActions = new List<CodeAction>();

// Collect all actions for all diagnostics
foreach (var diagnostic in diagnostics.OrderBy(d => d.Location.SourceSpan.Start))
var codeFixProvider = new ReactivePropertyCodeFixProvider();

// Apply fixes one at a time in a deterministic order
foreach (var diagnostic in diagnostics)
{
var actions = new List<CodeAction>();
var context = new CodeFixContext(document, diagnostic,
(a, _) => allActions.Add(a),
(a, _) =>
{
if (equivalenceKey == null || a.EquivalenceKey == equivalenceKey)
{
actions.Add(a);
}
},
CancellationToken.None);

await codeFixProvider.RegisterCodeFixesAsync(context);

if (actions.Any())
{
// Sort actions deterministically
actions = actions
.OrderBy(a => a.EquivalenceKey)
.ThenBy(a => a.Title)
.ToList();

var action = actions[0];

// Apply the fix
var operations = await action.GetOperationsAsync(CancellationToken.None);
var operation = operations.OfType<ApplyChangesOperation>().First();

// Get the new solution and document
project = operation.ChangedSolution.GetProject(project.Id)!;
document = project.Documents.First();

// Update the source for verification
newSource = (await document.GetTextAsync()).ToString();
}
}

// Apply all actions in sequence
foreach (var action in allActions)
{
var operations = await action.GetOperationsAsync(CancellationToken.None);
var operation = operations.OfType<ApplyChangesOperation>().Single();
var solution = operation.ChangedSolution;
document = solution.GetDocument(documentId);

// Update compilation to get fresh diagnostics
var text = await document.GetTextAsync();
compilation = CreateCompilation(text.ToString());
}

newSource = (await document.GetTextAsync()).ToString();
}

// Use Verify
// Verify results
await Verifier.Verify(new
{
Diagnostics = diagnostics.Select(d => new
Expand All @@ -80,9 +108,9 @@ await Verifier.Verify(new
d.Severity,
Location = d.Location.GetLineSpan().StartLinePosition,
Message = d.GetMessage()
}).OrderBy(d => d.Id).ThenBy(d => d.Location.Line),
}).OrderBy(d => d.Location.Line).ThenBy(d => d.Id),
FixedSource = newSource
});
}).UseDirectory("Snapshots");
}

private static Project CreateProject(string source)
Expand All @@ -101,7 +129,8 @@ private static Project CreateProject(string source)
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Attribute).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.ComponentModel.INotifyPropertyChanged).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.ComponentModel.INotifyPropertyChanged).Assembly
.Location),
MetadataReference.CreateFromFile(typeof(ReactiveUI.ReactiveObject).Assembly.Location)
};

Expand All @@ -116,7 +145,8 @@ private static Compilation CreateCompilation(string source)
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Attribute).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.ComponentModel.INotifyPropertyChanged).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.ComponentModel.INotifyPropertyChanged).Assembly
.Location),
MetadataReference.CreateFromFile(typeof(ReactiveUI.ReactiveObject).Assembly.Location)
};

Expand Down
Loading
Loading