-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implemented first pass at a
PipeTo(Sender)
CodeFixer (#15)
* implemented first pass at a `PipeTo(Sender)` CodeFixer * renaming existing Analyzer specs so there won't be naming conflicts or confusion with Fixer specs that target the same ruleset * refactored `Verify` to include possibility of CodeFix tests * rename analyzer test files * trying to debug * restore proper test behavior * fixed spec Roslyn is still not detecting the fixer correctly though * working on fixing "Fixer" discovery * removed duplicate project reference * fixed discovery * have fixer working, now fighting over whitespace * fixed whitespace mangling * removed offending lines from formatter * verified FixIt cases for PipeTo
- Loading branch information
1 parent
8850fa9
commit 5482a51
Showing
13 changed files
with
335 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
src/Akka.Analyzers.Fixes/AK1000/MustCloseOverSenderWhenUsingPipeToFixer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
using System.Composition; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Editing; | ||
using Microsoft.CodeAnalysis.Formatting; | ||
using static Akka.Analyzers.Fixes.CodeGeneratorUtilities; | ||
|
||
namespace Akka.Analyzers.Fixes.AK1000; | ||
|
||
[ExportCodeFixProvider(LanguageNames.CSharp), Shared] | ||
public sealed class MustCloseOverSenderWhenUsingPipeToFixer() | ||
: BatchedCodeFixProvider(RuleDescriptors.Ak1001CloseOverSenderUsingPipeTo.Id) | ||
{ | ||
public const string Key_FixPipeToSender = "AK1000_FixPipeToSender"; | ||
|
||
public override async Task RegisterCodeFixesAsync(CodeFixContext context) | ||
{ | ||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); | ||
if (root is null) | ||
return; | ||
|
||
var diagnostic = context.Diagnostics.FirstOrDefault(); | ||
if (diagnostic is null) | ||
return; | ||
var diagnosticSpan = diagnostic.Location.SourceSpan; | ||
|
||
// Find the PipeTo invocation expression | ||
var invocationExpr = root.FindToken(diagnosticSpan.Start).Parent?.AncestorsAndSelf() | ||
.OfType<InvocationExpressionSyntax>().First(); | ||
if (invocationExpr is null) | ||
return; | ||
|
||
context.RegisterCodeFix( | ||
CodeAction.Create( | ||
title: "Use local variable for Sender", | ||
createChangedDocument: c => UseLocalVariableForSenderAsync(context.Document, invocationExpr, c), | ||
equivalenceKey: Key_FixPipeToSender), | ||
context.Diagnostics); | ||
} | ||
|
||
private static async Task<Document> UseLocalVariableForSenderAsync(Document document, | ||
InvocationExpressionSyntax invocationExpr, CancellationToken cancellationToken) | ||
{ | ||
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); | ||
|
||
// Generate a local variable to hold 'Sender' | ||
var senderVariable = IntroduceLocalVariableStatement("sender", SyntaxFactory.MemberAccessExpression( | ||
SyntaxKind.SimpleMemberAccessExpression, | ||
SyntaxFactory.ThisExpression(), | ||
SyntaxFactory.IdentifierName("Sender"))) | ||
.WithAdditionalAnnotations(Formatter.Annotation); // need this line to get indentation right | ||
|
||
// Find an appropriate insertion point for the local variable | ||
var insertionPoint = invocationExpr.FirstAncestorOrSelf<StatementSyntax>(); | ||
|
||
if (insertionPoint == null) | ||
{ | ||
// Unable to find a valid insertion point | ||
return document; | ||
} | ||
|
||
// Insert the local variable declaration at the found insertion point | ||
editor.InsertBefore(insertionPoint, senderVariable); | ||
|
||
// in the invocationExpr, replace the old argument list with a new one that uses the new local variable | ||
// Replace the invocation arguments with the new local variable | ||
var newArgumentList = SyntaxFactory.ArgumentList( | ||
SyntaxFactory.SingletonSeparatedList( | ||
SyntaxFactory.Argument( | ||
SyntaxFactory.IdentifierName("sender")))); | ||
|
||
var newInvocationExpr = invocationExpr.WithArgumentList(newArgumentList); | ||
|
||
// Make sure to replace the old invocation with the new one | ||
editor.ReplaceNode(invocationExpr, newInvocationExpr); | ||
|
||
var newDocument = editor.GetChangedDocument(); // error happens here | ||
|
||
return newDocument; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netstandard2.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Akka.Analyzers\Akka.Analyzers.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
2 changes: 2 additions & 0 deletions
2
src/Akka.Analyzers.Fixes/Akka.Analyzers.Fixes.csproj.DotSettings
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> | ||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=utility/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> |
12 changes: 12 additions & 0 deletions
12
src/Akka.Analyzers.Fixes/Utility/BatchedCodeFixProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
using System.Collections.Immutable; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
|
||
namespace Akka.Analyzers.Fixes; | ||
|
||
public abstract class BatchedCodeFixProvider(params string[] diagnostics) : CodeFixProvider | ||
{ | ||
public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = diagnostics.ToImmutableArray(); | ||
|
||
public sealed override FixAllProvider GetFixAllProvider() => | ||
WellKnownFixAllProviders.BatchFixer; | ||
} |
31 changes: 31 additions & 0 deletions
31
src/Akka.Analyzers.Fixes/Utility/CodeGeneratorUtilities.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// ----------------------------------------------------------------------- | ||
// <copyright file="CodeGeneratorUtilities.cs" company="Akka.NET Project"> | ||
// Copyright (C) 2015-2023 .NET Petabridge, LLC | ||
// </copyright> | ||
// ----------------------------------------------------------------------- | ||
|
||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; | ||
|
||
namespace Akka.Analyzers.Fixes; | ||
|
||
internal static class CodeGeneratorUtilities | ||
{ | ||
public static LocalDeclarationStatementSyntax IntroduceLocalVariableStatement( | ||
string parameterName, | ||
ExpressionSyntax replacementNode) | ||
{ | ||
var equalsToReplacementNode = EqualsValueClause(replacementNode); | ||
|
||
var oneItemVariableDeclaration = VariableDeclaration( | ||
ParseTypeName("var"), | ||
SingletonSeparatedList( | ||
VariableDeclarator(Identifier(parameterName)) | ||
.WithInitializer(equalsToReplacementNode) | ||
) | ||
); | ||
|
||
return LocalDeclarationStatement(oneItemVariableDeclaration); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
107 changes: 107 additions & 0 deletions
107
src/Akka.Analyzers.Tests/Fixes/AK1000/MustCloseOverSenderWhenUsingPipeToFixerSpecs.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
using Akka.Analyzers.Fixes.AK1000; | ||
using Verify = Akka.Analyzers.Tests.Utility.AkkaVerifier<Akka.Analyzers.MustCloseOverSenderWhenUsingPipeToAnalyzer>; | ||
|
||
namespace Akka.Analyzers.Tests.Fixes.AK1000; | ||
|
||
public class MustCloseOverSenderWhenUsingPipeToFixerSpecs | ||
{ | ||
[Fact] | ||
public Task AddClosureInsideReceiveActor() | ||
{ | ||
var before = | ||
@"using Akka.Actor; | ||
using System.Threading.Tasks; | ||
public sealed class MyActor : ReceiveActor{ | ||
public MyActor(){ | ||
Receive<string>(str => { | ||
async Task<int> LocalFunction(){ | ||
await Task.Delay(10); | ||
return str.Length; | ||
} | ||
// incorrect use of closure | ||
LocalFunction().PipeTo(Sender); | ||
}); | ||
} | ||
}"; | ||
|
||
var after = | ||
@"using Akka.Actor; | ||
using System.Threading.Tasks; | ||
public sealed class MyActor : ReceiveActor{ | ||
public MyActor(){ | ||
Receive<string>(str => { | ||
async Task<int> LocalFunction(){ | ||
await Task.Delay(10); | ||
return str.Length; | ||
} | ||
var sender = this.Sender; | ||
// incorrect use of closure | ||
LocalFunction().PipeTo(sender); | ||
}); | ||
} | ||
}"; | ||
|
||
var expectedDiagnostic = Verify.Diagnostic() | ||
.WithSpan(14, 29, 14, 35) | ||
.WithArguments("Sender"); | ||
|
||
return Verify.VerifyCodeFix(before, after, MustCloseOverSenderWhenUsingPipeToFixer.Key_FixPipeToSender, | ||
expectedDiagnostic); | ||
} | ||
|
||
[Fact] | ||
public Task AddClosureInsideUntypedActor() | ||
{ | ||
var before = | ||
@"using Akka.Actor; | ||
using System.Threading.Tasks; | ||
using System; | ||
public sealed class MyActor : UntypedActor{ | ||
protected override void OnReceive(object message){ | ||
async Task<int> LocalFunction(){ | ||
await Task.Delay(10); | ||
return message.ToString().Length; | ||
} | ||
Console.WriteLine(Sender); | ||
// incorrect use of closure | ||
LocalFunction().PipeTo(Sender); | ||
} | ||
}"; | ||
|
||
var after = | ||
@"using Akka.Actor; | ||
using System.Threading.Tasks; | ||
using System; | ||
public sealed class MyActor : UntypedActor{ | ||
protected override void OnReceive(object message){ | ||
async Task<int> LocalFunction(){ | ||
await Task.Delay(10); | ||
return message.ToString().Length; | ||
} | ||
Console.WriteLine(Sender); | ||
var sender = this.Sender; | ||
// incorrect use of closure | ||
LocalFunction().PipeTo(sender); | ||
} | ||
}"; | ||
|
||
var expectedDiagnostic = Verify.Diagnostic() | ||
.WithSpan(15, 25, 15, 31) | ||
.WithArguments("Sender"); | ||
|
||
return Verify.VerifyCodeFix(before, after, MustCloseOverSenderWhenUsingPipeToFixer.Key_FixPipeToSender, | ||
expectedDiagnostic); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.