Skip to content

Commit efd7e2d

Browse files
authored
Merge pull request #2 from PandaTechAM/development
bug fixes
2 parents 97c96aa + ff91687 commit efd7e2d

File tree

5 files changed

+279
-36
lines changed

5 files changed

+279
-36
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.1.0</Version>
21+
<Version>1.2.0</Version>
2222
<Authors>Pandatech</Authors>
2323
<Description>Pandatech Roslyn analyzers enforcing company coding rules.</Description>
2424
<PackageLicenseExpression>MIT</PackageLicenseExpression>

src/Analyzers/Async/AsyncMethodConventionsAnalyzer.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,23 @@ private static CtInfo GetCancellationTokenInfo(IMethodSymbol method)
224224
}
225225

226226
var isNamedCt = string.Equals(ctParam.Name, "ct", StringComparison.Ordinal);
227-
var isLast = ctParam.Ordinal == parameters.Length - 1;
227+
228+
// "Last" means: last non-params parameter (params must stay physically last)
229+
var lastNonParamsIndex = -1;
230+
for (var i = 0; i < parameters.Length; i++)
231+
{
232+
if (!parameters[i].IsParams)
233+
{
234+
lastNonParamsIndex = i;
235+
}
236+
}
237+
238+
var isLast = ctParam.Ordinal == lastNonParamsIndex;
228239

229240
return new CtInfo(true, isNamedCt, isLast, ctParam.Name);
230241
}
231242

243+
232244
private readonly struct CtInfo(bool hasCt, bool isNamedCt, bool isLast, string name)
233245
{
234246
public bool HasCt { get; } = hasCt;

src/Analyzers/Async/AsyncMethodConventionsCodeFixProvider.cs

Lines changed: 155 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -418,17 +418,40 @@ private static async Task<Document> AddCancellationTokenAsync(Document document,
418418
newRoot = newRoot.AddUsings(usingDirective);
419419
}
420420

421+
var parameters = methodDecl.ParameterList.Parameters;
422+
421423
var ctType = SyntaxFactory.IdentifierName("CancellationToken");
422424
var ctParam = SyntaxFactory.Parameter(SyntaxFactory.Identifier("ct"))
423-
.WithType(ctType);
425+
.WithType(ctType)
426+
.WithDefault(
427+
SyntaxFactory.EqualsValueClause(
428+
SyntaxFactory.LiteralExpression(SyntaxKind.DefaultLiteralExpression)));
429+
430+
// Insert CT before any params parameter; otherwise at the end.
431+
var insertIndex = parameters.Count;
432+
for (var i = 0; i < parameters.Count; i++)
433+
{
434+
if (!parameters[i]
435+
.Modifiers
436+
.Any(SyntaxKind.ParamsKeyword))
437+
{
438+
continue;
439+
}
440+
441+
insertIndex = i;
442+
break;
443+
}
424444

425-
var newParamList = methodDecl.ParameterList.AddParameters(ctParam);
426-
var newMethod = methodDecl.WithParameterList(newParamList);
445+
var newParameters = parameters.Insert(insertIndex, ctParam);
446+
447+
var newMethod = methodDecl.WithParameterList(
448+
methodDecl.ParameterList.WithParameters(newParameters));
427449

428450
newRoot = newRoot.ReplaceNode(methodDecl, newMethod);
429451
return document.WithSyntaxRoot(newRoot);
430452
}
431453

454+
432455
private static bool HasSystemThreadingUsing(CompilationUnitSyntax root)
433456
{
434457
foreach (var u in root.Usings)
@@ -468,14 +491,61 @@ private static async Task<Document> MoveCancellationTokenToLastAsync(Document do
468491
break;
469492
}
470493

471-
if (ctIndex < 0 || ctIndex == parameters.Count - 1)
494+
if (ctIndex < 0)
495+
{
496+
return document;
497+
}
498+
499+
// Find params parameter (if any)
500+
var paramsIndex = -1;
501+
for (var i = 0; i < parameters.Count; i++)
502+
{
503+
if (!parameters[i]
504+
.Modifiers
505+
.Any(SyntaxKind.ParamsKeyword))
506+
{
507+
continue;
508+
}
509+
510+
paramsIndex = i;
511+
break;
512+
}
513+
514+
// Already in the correct spot?
515+
var alreadyCorrect =
516+
(paramsIndex < 0 && ctIndex == parameters.Count - 1) ||
517+
(paramsIndex >= 0 && ctIndex == paramsIndex - 1);
518+
519+
if (alreadyCorrect)
472520
{
473521
return document;
474522
}
475523

476524
var ctSyntax = parameters[ctIndex];
477-
var newParameters = parameters.RemoveAt(ctIndex)
478-
.Add(ctSyntax);
525+
var withoutCt = parameters.RemoveAt(ctIndex);
526+
527+
// Recompute params index after removal
528+
var newParamsIndex = -1;
529+
for (var i = 0; i < withoutCt.Count; i++)
530+
{
531+
if (!withoutCt[i]
532+
.Modifiers
533+
.Any(SyntaxKind.ParamsKeyword))
534+
{
535+
continue;
536+
}
537+
538+
newParamsIndex = i;
539+
break;
540+
}
541+
542+
// Insert CT just before params
543+
var newParameters =
544+
newParamsIndex >= 0
545+
? withoutCt.Insert(newParamsIndex, ctSyntax)
546+
:
547+
// No params -> CT becomes last
548+
withoutCt.Add(ctSyntax);
479549

480550
var newMethod = methodDecl.WithParameterList(
481551
methodDecl.ParameterList.WithParameters(newParameters));
@@ -491,6 +561,7 @@ private static async Task<Document> MoveCancellationTokenToLastAsync(Document do
491561
return document.WithSyntaxRoot(newRoot);
492562
}
493563

564+
494565
// ----------------------------------------------------------------------
495566
// Syntax-level helpers – lambdas / anonymous functions
496567
// ----------------------------------------------------------------------
@@ -659,20 +730,34 @@ private static async Task<Solution> AddCancellationTokenForInterfaceAndImplement
659730
}
660731
}
661732

662-
// Build parameter differently for interface vs implementation
663-
var ctParam = SyntaxFactory.Parameter(SyntaxFactory.Identifier("ct"))
664-
.WithType(ctType);
733+
var parameters = original.ParameterList.Parameters;
665734

666-
if (original.Parent is InterfaceDeclarationSyntax)
735+
// Always: CancellationToken ct = default
736+
var ctParam = SyntaxFactory.Parameter(SyntaxFactory.Identifier("ct"))
737+
.WithType(ctType)
738+
.WithDefault(
739+
SyntaxFactory.EqualsValueClause(
740+
SyntaxFactory.LiteralExpression(SyntaxKind.DefaultLiteralExpression)));
741+
742+
// Insert CT before any params parameter; otherwise at the end.
743+
var insertIndex = parameters.Count;
744+
for (var i = 0; i < parameters.Count; i++)
667745
{
668-
// interface: CancellationToken ct = default
669-
ctParam = ctParam.WithDefault(
670-
SyntaxFactory.EqualsValueClause(
671-
SyntaxFactory.LiteralExpression(SyntaxKind.DefaultLiteralExpression)));
746+
if (!parameters[i]
747+
.Modifiers
748+
.Any(SyntaxKind.ParamsKeyword))
749+
{
750+
continue;
751+
}
752+
753+
insertIndex = i;
754+
break;
672755
}
673756

674-
var newParamList = original.ParameterList.AddParameters(ctParam);
675-
return original.WithParameterList(newParamList);
757+
var newParameters = parameters.Insert(insertIndex, ctParam);
758+
759+
return original.WithParameterList(
760+
original.ParameterList.WithParameters(newParameters));
676761
});
677762

678763
solution = solution.WithDocumentSyntaxRoot(docId, updatedRoot);
@@ -705,21 +790,9 @@ private static async Task<Solution> MoveCancellationTokenToLastForInterfaceAndIm
705790
}
706791

707792
var methodsByDocument = new Dictionary<DocumentId, ImmutableArray<MethodDeclarationSyntax>.Builder>();
708-
var ctIndexBySyntax = new Dictionary<MethodDeclarationSyntax, int>();
709793

710794
foreach (var method in allMethods)
711795
{
712-
var ctParam = method.Parameters.FirstOrDefault(p => IsCancellationToken(p.Type));
713-
if (ctParam is null)
714-
{
715-
continue;
716-
}
717-
718-
if (ctParam.Ordinal == method.Parameters.Length - 1)
719-
{
720-
continue;
721-
}
722-
723796
foreach (var syntaxRef in method.DeclaringSyntaxReferences)
724797
{
725798
var syntax = await syntaxRef.GetSyntaxAsync(cancellationToken)
@@ -744,7 +817,6 @@ private static async Task<Solution> MoveCancellationTokenToLastForInterfaceAndIm
744817
}
745818

746819
list.Add(methodDecl);
747-
ctIndexBySyntax[methodDecl] = ctParam.Ordinal;
748820
}
749821
}
750822

@@ -770,20 +842,69 @@ private static async Task<Solution> MoveCancellationTokenToLastForInterfaceAndIm
770842
methodDecls,
771843
(original, _) =>
772844
{
773-
if (!ctIndexBySyntax.TryGetValue(original, out var ctIndex))
845+
var parameters = original.ParameterList.Parameters;
846+
847+
// Find CT parameter syntax
848+
var ctIndex = -1;
849+
for (var i = 0; i < parameters.Count; i++)
850+
{
851+
var p = parameters[i];
852+
if (p.Type is IdentifierNameSyntax id &&
853+
string.Equals(id.Identifier.Text, "CancellationToken", StringComparison.Ordinal))
854+
{
855+
ctIndex = i;
856+
break;
857+
}
858+
}
859+
860+
if (ctIndex < 0)
774861
{
775862
return original;
776863
}
777864

778-
var parameters = original.ParameterList.Parameters;
779-
if (ctIndex < 0 || ctIndex >= parameters.Count || ctIndex == parameters.Count - 1)
865+
// Find params parameter
866+
var paramsIndex = -1;
867+
for (var i = 0; i < parameters.Count; i++)
868+
{
869+
if (parameters[i]
870+
.Modifiers
871+
.Any(SyntaxKind.ParamsKeyword))
872+
{
873+
paramsIndex = i;
874+
break;
875+
}
876+
}
877+
878+
var alreadyCorrect =
879+
(paramsIndex < 0 && ctIndex == parameters.Count - 1) ||
880+
(paramsIndex >= 0 && ctIndex == paramsIndex - 1);
881+
882+
if (alreadyCorrect)
780883
{
781884
return original;
782885
}
783886

784887
var ctSyntax = parameters[ctIndex];
785-
var newParameters = parameters.RemoveAt(ctIndex)
786-
.Add(ctSyntax);
888+
var withoutCt = parameters.RemoveAt(ctIndex);
889+
890+
// Recompute params index after removal
891+
var newParamsIndex = -1;
892+
for (var i = 0; i < withoutCt.Count; i++)
893+
{
894+
if (!withoutCt[i]
895+
.Modifiers
896+
.Any(SyntaxKind.ParamsKeyword))
897+
{
898+
continue;
899+
}
900+
901+
newParamsIndex = i;
902+
break;
903+
}
904+
905+
var newParameters = newParamsIndex >= 0
906+
? withoutCt.Insert(newParamsIndex, ctSyntax)
907+
: withoutCt.Add(ctSyntax);
787908

788909
return original.WithParameterList(
789910
original.ParameterList.WithParameters(newParameters));

test/Analyzers.Sample/Async/Examples1.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ 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);
22+
Task<Order> GetMeAsync(int id = 5, params object[] args);
2123
}
2224

2325
public sealed class OrderService : IOrderService
@@ -38,6 +40,16 @@ public Task<Order> GetOrderByCodeAsync(CancellationToken token, string code)
3840
return Task.FromResult(new Order(42));
3941
// PT0002
4042
}
43+
44+
public Task<Order> GetSomethingAsync(params string[] args)
45+
{
46+
throw new System.NotImplementedException();
47+
}
48+
49+
public Task<Order> GetMeAsync(int id = 5, params object[] args)
50+
{
51+
throw new System.NotImplementedException();
52+
}
4153
}
4254

4355
// Simulated controller-style class.

0 commit comments

Comments
 (0)