Skip to content

Commit cfcb848

Browse files
authored
Merge pull request #5 from PandaTechAM/development
Small fix
2 parents 3d96b9e + 32e23ac commit cfcb848

File tree

4 files changed

+154
-4
lines changed

4 files changed

+154
-4
lines changed

src/Analyzers/Analyzers.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<PackageId>Pandatech.Analyzers</PackageId>
1919
<PackageIcon>pandatech.png</PackageIcon>
2020
<PackageReadmeFile>Readme.md</PackageReadmeFile>
21-
<Version>1.4.1</Version>
21+
<Version>1.4.2</Version>
2222
<Authors>Pandatech</Authors>
2323
<Description>Pandatech Roslyn analyzers enforcing company coding rules.</Description>
2424
<PackageLicenseExpression>MIT</PackageLicenseExpression>

src/Analyzers/Async/AsyncMethodConventionsCodeFixProvider.cs

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,31 @@ when string.Equals(ctParam.Name, "ct", StringComparison.Ordinal):
214214

215215
case AsyncMethodConventionsAnalyzer.CancellationTokenNameId:
216216
{
217+
var isInterfaceMethod = methodSymbol.ContainingType?.TypeKind == TypeKind.Interface;
218+
219+
if (isInterfaceMethod)
220+
{
221+
const string title = "Rename CancellationToken parameter to 'ct' (interface and implementations)";
222+
223+
context.RegisterCodeFix(
224+
CodeAction.Create(
225+
title,
226+
c => RenameCancellationTokenForInterfaceAndImplementationsAsync(
227+
context.Document.Project.Solution,
228+
methodSymbol,
229+
c),
230+
"RenameCt_InterfaceAndImpls"),
231+
diagnostic);
232+
233+
return;
234+
}
235+
236+
// Non-interface method: simple symbol rename
237+
if (string.Equals(ctParam.Name, "ct", StringComparison.Ordinal))
238+
{
239+
return;
240+
}
241+
217242
var renameTitle = $"Rename '{ctParam.Name}' to 'ct'";
218243

219244
context.RegisterCodeFix(
@@ -959,4 +984,129 @@ private static async Task<Document> MoveCancellationTokenToLastInLambdaAsync(Doc
959984
var newRoot = root.ReplaceNode(parenthesized, newLambda);
960985
return document.WithSyntaxRoot(newRoot);
961986
}
987+
988+
private static async Task<Solution> RenameCancellationTokenForInterfaceAndImplementationsAsync(Solution solution,
989+
IMethodSymbol interfaceMethod,
990+
CancellationToken cancellationToken)
991+
{
992+
// 1. Find CT parameter ordinal on the interface
993+
var ctIndex = -1;
994+
for (var i = 0; i < interfaceMethod.Parameters.Length; i++)
995+
{
996+
if (IsCancellationToken(interfaceMethod.Parameters[i].Type))
997+
{
998+
ctIndex = i;
999+
break;
1000+
}
1001+
}
1002+
1003+
if (ctIndex < 0)
1004+
{
1005+
// Interface no longer has CT? Nothing to do.
1006+
return solution;
1007+
}
1008+
1009+
// 2. Collect interface + implementing methods
1010+
var allMethods = ImmutableArray.CreateBuilder<IMethodSymbol>();
1011+
allMethods.Add(interfaceMethod);
1012+
1013+
var impls = await SymbolFinder.FindImplementationsAsync(
1014+
interfaceMethod,
1015+
solution,
1016+
projects: null,
1017+
cancellationToken)
1018+
.ConfigureAwait(false);
1019+
1020+
foreach (var impl in impls.OfType<IMethodSymbol>())
1021+
{
1022+
if (impl.DeclaringSyntaxReferences.Length > 0)
1023+
{
1024+
allMethods.Add(impl);
1025+
}
1026+
}
1027+
1028+
// 3. Group by document
1029+
var methodsByDocument = new Dictionary<DocumentId, ImmutableArray<MethodDeclarationSyntax>.Builder>();
1030+
1031+
foreach (var method in allMethods)
1032+
{
1033+
foreach (var syntaxRef in method.DeclaringSyntaxReferences)
1034+
{
1035+
var syntax = await syntaxRef.GetSyntaxAsync(cancellationToken)
1036+
.ConfigureAwait(false);
1037+
if (syntax is not MethodDeclarationSyntax methodDecl)
1038+
{
1039+
continue;
1040+
}
1041+
1042+
var doc = solution.GetDocument(methodDecl.SyntaxTree);
1043+
if (doc is null)
1044+
{
1045+
continue;
1046+
}
1047+
1048+
var docId = doc.Id;
1049+
if (!methodsByDocument.TryGetValue(docId, out var list))
1050+
{
1051+
list = ImmutableArray.CreateBuilder<MethodDeclarationSyntax>();
1052+
methodsByDocument[docId] = list;
1053+
}
1054+
1055+
list.Add(methodDecl);
1056+
}
1057+
}
1058+
1059+
// 4. Rewrite name in each method declaration at the same ordinal
1060+
foreach (var kvp in methodsByDocument)
1061+
{
1062+
var docId = kvp.Key;
1063+
var methodDecls = kvp.Value.ToImmutable();
1064+
1065+
var document = solution.GetDocument(docId);
1066+
if (document is null)
1067+
{
1068+
continue;
1069+
}
1070+
1071+
var root = await document.GetSyntaxRootAsync(cancellationToken)
1072+
.ConfigureAwait(false);
1073+
if (root is null)
1074+
{
1075+
continue;
1076+
}
1077+
1078+
var newRoot = root.ReplaceNodes(
1079+
methodDecls,
1080+
(original, _) =>
1081+
{
1082+
var parameters = original.ParameterList.Parameters;
1083+
if (ctIndex < 0 || ctIndex >= parameters.Count)
1084+
{
1085+
// Signature drifted; be conservative.
1086+
return original;
1087+
}
1088+
1089+
var ctParamSyntax = parameters[ctIndex];
1090+
1091+
// If it's already 'ct', no change needed.
1092+
if (ctParamSyntax.Identifier.Text == "ct")
1093+
{
1094+
return original;
1095+
}
1096+
1097+
var newCtParamSyntax = ctParamSyntax.WithIdentifier(
1098+
SyntaxFactory.Identifier("ct")
1099+
.WithTriviaFrom(ctParamSyntax.Identifier));
1100+
1101+
var newParameters = parameters.Replace(ctParamSyntax, newCtParamSyntax);
1102+
1103+
return original.WithParameterList(
1104+
original.ParameterList.WithParameters(newParameters));
1105+
});
1106+
1107+
solution = solution.WithDocumentSyntaxRoot(docId, newRoot);
1108+
}
1109+
1110+
return solution;
1111+
}
9621112
}

test/Analyzers.Sample/Async/Examples1.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public interface IOrderService
1818

1919
// ❌ PT0002 (CT is not last, name is wrong)
2020
Task<Order> GetOrderByCodeAsync(CancellationToken token, string code);
21-
Task<Order> GetSomethingAsync(params string[] args);
21+
Task<Order> GetSomethingAsync(CancellationToken ct = default, params string[] args);
2222
Task<Order> GetMeAsync(int id = 5, params object[] args);
2323
}
2424

@@ -41,7 +41,7 @@ public Task<Order> GetOrderByCodeAsync(CancellationToken token, string code)
4141
// PT0002
4242
}
4343

44-
public Task<Order> GetSomethingAsync(params string[] args)
44+
public Task<Order> GetSomethingAsync(CancellationToken ct = default, params string[] args)
4545
{
4646
throw new System.NotImplementedException();
4747
}

test/Analyzers.Sample/Async/Examples2.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public class MediatRequestHandler : IRequestHandler<MediatRequest>
1010
{
1111
// This method should not be renamed to HandleAsync
1212

13-
public Task Handle(MediatRequest request, CancellationToken ct)
13+
public Task Handle(MediatRequest request, CancellationToken cancellationToken)
1414
{
1515
throw new System.NotImplementedException();
1616
}

0 commit comments

Comments
 (0)