Skip to content

Commit 8660808

Browse files
authored
Add code fix for AK1008 (#117)
1 parent cf58099 commit 8660808

File tree

2 files changed

+269
-0
lines changed

2 files changed

+269
-0
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="ShouldNotUseSystemToCreateChildActorsFixer.cs" company="Akka.NET Project">
3+
// Copyright (C) 2013-2025 .NET Foundation <https://github.com/akkadotnet/akka.net>
4+
// </copyright>
5+
// -----------------------------------------------------------------------
6+
7+
using System.Composition;
8+
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.CodeActions;
10+
using Microsoft.CodeAnalysis.CodeFixes;
11+
using Microsoft.CodeAnalysis.CSharp;
12+
using Microsoft.CodeAnalysis.CSharp.Syntax;
13+
using Microsoft.CodeAnalysis.Editing;
14+
15+
namespace Akka.Analyzers.Fixes;
16+
17+
[ExportCodeFixProvider(LanguageNames.CSharp)]
18+
[Shared]
19+
public class ShouldNotUseSystemToCreateChildActorsFixer()
20+
: BatchedCodeFixProvider(RuleDescriptors.Ak1008ShouldNotUseSystemToCreateChildActor.Id)
21+
{
22+
public const string Key_FixActorSystemActorOf = "AK1008_FixActorSystemActorOf";
23+
24+
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
25+
{
26+
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
27+
if (root is null)
28+
return;
29+
30+
var diagnostic = context.Diagnostics.FirstOrDefault();
31+
if (diagnostic is null)
32+
return;
33+
var diagnosticSpan = diagnostic.Location.SourceSpan;
34+
35+
// Find the invocation expression
36+
if(root.FindNode(diagnosticSpan) is not InvocationExpressionSyntax invocationExpr)
37+
return;
38+
39+
context.RegisterCodeFix(
40+
CodeAction.Create(
41+
"Remove await keyword",
42+
c => ReplaceSystemWithContextAsync(context.Document, invocationExpr, c),
43+
Key_FixActorSystemActorOf),
44+
context.Diagnostics);
45+
}
46+
47+
private static async Task<Document> ReplaceSystemWithContextAsync(
48+
Document document,
49+
InvocationExpressionSyntax invocationExpr,
50+
CancellationToken cancellationToken)
51+
{
52+
// We expect the offending call to look like: Context.System.ActorOf(...)
53+
// That means invocationNode.Expression is a MemberAccessExpressionSyntax.
54+
if (invocationExpr.Expression is not MemberAccessExpressionSyntax memberAccess)
55+
return document;
56+
57+
// Create a new receiver identifier: "Context"
58+
var newReceiver = SyntaxFactory.IdentifierName("Context")
59+
.WithLeadingTrivia(memberAccess.Expression.GetLeadingTrivia())
60+
.WithTrailingTrivia(memberAccess.Expression.GetTrailingTrivia());
61+
62+
// Build a new member access expression using the new receiver and preserving the operator and the invoked member name.
63+
var newMemberAccess = SyntaxFactory.MemberAccessExpression(
64+
SyntaxKind.SimpleMemberAccessExpression,
65+
newReceiver,
66+
memberAccess.OperatorToken,
67+
memberAccess.Name)
68+
.WithLeadingTrivia(memberAccess.GetLeadingTrivia())
69+
.WithTrailingTrivia(memberAccess.GetTrailingTrivia());
70+
71+
// Replace the original invocation's expression with the new member access.
72+
var newInvocation = invocationExpr.WithExpression(newMemberAccess);
73+
74+
// Use DocumentEditor to perform the replacement.
75+
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
76+
editor.ReplaceNode(invocationExpr, newInvocation);
77+
78+
return editor.GetChangedDocument();
79+
}
80+
}
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="ShouldNotUseSystemToCreateChildActorsFixerSpecs.cs" company="Akka.NET Project">
3+
// Copyright (C) 2013-2025 .NET Foundation <https://github.com/akkadotnet/akka.net>
4+
// </copyright>
5+
// -----------------------------------------------------------------------
6+
7+
using Akka.Analyzers.Fixes;
8+
using Verify = Akka.Analyzers.Tests.Utility.AkkaVerifier<Akka.Analyzers.ShouldNotUseSystemToCreateChildActorsAnalyzer>;
9+
10+
namespace Akka.Analyzers.Tests.Fixes.AK1000;
11+
12+
public class ShouldNotUseSystemToCreateChildActorsFixerSpecs
13+
{
14+
[Fact]
15+
public Task RemoveSystemMemberAccessAsync()
16+
{
17+
const string before =
18+
"""
19+
using Akka.Actor;
20+
21+
public class MyActor : ReceiveActor
22+
{
23+
public MyActor()
24+
{
25+
Context.System.ActorOf<ChildActor>();
26+
}
27+
}
28+
29+
public class ChildActor : ReceiveActor
30+
{
31+
}
32+
""";
33+
34+
const string after =
35+
"""
36+
using Akka.Actor;
37+
38+
public class MyActor : ReceiveActor
39+
{
40+
public MyActor()
41+
{
42+
Context.ActorOf<ChildActor>();
43+
}
44+
}
45+
46+
public class ChildActor : ReceiveActor
47+
{
48+
}
49+
""";
50+
51+
var expectedDiagnostic = Verify.Diagnostic()
52+
.WithSpan(7, 9, 7, 45);
53+
54+
return Verify.VerifyCodeFix(before, after, ShouldNotUseSystemToCreateChildActorsFixer.Key_FixActorSystemActorOf,
55+
expectedDiagnostic);
56+
}
57+
58+
[Fact]
59+
public Task RemoveSystemMemberAccessVariantAsync()
60+
{
61+
const string before =
62+
"""
63+
using Akka.Actor;
64+
65+
public class MyActor : ReceiveActor
66+
{
67+
public MyActor()
68+
{
69+
Context.System.ActorOf(Props.Create(() => new ChildActor()));
70+
}
71+
}
72+
73+
public class ChildActor : ReceiveActor
74+
{
75+
}
76+
""";
77+
78+
const string after =
79+
"""
80+
using Akka.Actor;
81+
82+
public class MyActor : ReceiveActor
83+
{
84+
public MyActor()
85+
{
86+
Context.ActorOf(Props.Create(() => new ChildActor()));
87+
}
88+
}
89+
90+
public class ChildActor : ReceiveActor
91+
{
92+
}
93+
""";
94+
95+
var expectedDiagnostic = Verify.Diagnostic()
96+
.WithSpan(7, 9, 7, 69);
97+
98+
return Verify.VerifyCodeFix(before, after, ShouldNotUseSystemToCreateChildActorsFixer.Key_FixActorSystemActorOf,
99+
expectedDiagnostic);
100+
}
101+
102+
[Fact]
103+
public Task ChangeActorSystemMemberAccessToContextAsync()
104+
{
105+
const string before =
106+
"""
107+
using Akka.Actor;
108+
109+
public class MyActor : ReceiveActor
110+
{
111+
public MyActor(ActorSystem system)
112+
{
113+
system.ActorOf<ChildActor>();
114+
}
115+
}
116+
117+
public class ChildActor : ReceiveActor
118+
{
119+
}
120+
""";
121+
122+
const string after =
123+
"""
124+
using Akka.Actor;
125+
126+
public class MyActor : ReceiveActor
127+
{
128+
public MyActor(ActorSystem system)
129+
{
130+
Context.ActorOf<ChildActor>();
131+
}
132+
}
133+
134+
public class ChildActor : ReceiveActor
135+
{
136+
}
137+
""";
138+
139+
var expectedDiagnostic = Verify.Diagnostic()
140+
.WithSpan(7, 9, 7, 37);
141+
142+
return Verify.VerifyCodeFix(before, after, ShouldNotUseSystemToCreateChildActorsFixer.Key_FixActorSystemActorOf,
143+
expectedDiagnostic);
144+
}
145+
146+
[Fact]
147+
public Task ChangeActorSystemMemberAccessToContextVariantAsync()
148+
{
149+
const string before =
150+
"""
151+
using Akka.Actor;
152+
153+
public class MyActor : ReceiveActor
154+
{
155+
public MyActor(ActorSystem system)
156+
{
157+
system.ActorOf(Props.Create(() => new ChildActor()));
158+
}
159+
}
160+
161+
public class ChildActor : ReceiveActor
162+
{
163+
}
164+
""";
165+
166+
const string after =
167+
"""
168+
using Akka.Actor;
169+
170+
public class MyActor : ReceiveActor
171+
{
172+
public MyActor(ActorSystem system)
173+
{
174+
Context.ActorOf(Props.Create(() => new ChildActor()));
175+
}
176+
}
177+
178+
public class ChildActor : ReceiveActor
179+
{
180+
}
181+
""";
182+
183+
var expectedDiagnostic = Verify.Diagnostic()
184+
.WithSpan(7, 9, 7, 61);
185+
186+
return Verify.VerifyCodeFix(before, after, ShouldNotUseSystemToCreateChildActorsFixer.Key_FixActorSystemActorOf,
187+
expectedDiagnostic);
188+
}
189+
}

0 commit comments

Comments
 (0)