Skip to content

Commit 90287c6

Browse files
authored
Merge pull request #3289 from wdolek/bugfix/3279-handle-optional-named-args-correctly
SA1130: Handle optional named arguments correctly
2 parents a559b3e + 64a563c commit 90287c6

File tree

3 files changed

+104
-2
lines changed

3 files changed

+104
-2
lines changed

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1130CodeFixProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ private static ImmutableArray<string> GetMethodInvocationArgumentList(SemanticMo
174174
var originalInvocableExpression = argumentListSyntax.Parent;
175175

176176
var originalSymbolInfo = semanticModel.GetSymbolInfo(originalInvocableExpression);
177-
var argumentIndex = argumentListSyntax.Arguments.IndexOf(argumentSyntax);
177+
var argumentIndex = SA1130UseLambdaSyntax.FindParameterIndex(originalSymbolInfo, argumentSyntax, argumentListSyntax);
178178
var parameterList = SA1130UseLambdaSyntax.GetDelegateParameterList(originalSymbolInfo.Symbol, argumentIndex);
179179
return parameterList.Parameters.Select(p => p.Identifier.ToString()).ToImmutableArray();
180180
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1130UnitTests.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,5 +880,86 @@ public class TestClass
880880

881881
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
882882
}
883+
884+
[Fact]
885+
[WorkItem(3279, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3279")]
886+
public async Task TestDelegateUsedAsSecondNamedArgumentAsync()
887+
{
888+
var testCode = @"
889+
using System;
890+
using System.Linq;
891+
public class TypeName
892+
{
893+
public void Test()
894+
{
895+
Test2(resolve: delegate
896+
{
897+
return """";
898+
});
899+
}
900+
901+
private void Test2(string description = null, Func<object, string> resolve = null)
902+
{
903+
resolve(0);
904+
}
905+
}";
906+
907+
string fixedCode = @"
908+
using System;
909+
using System.Linq;
910+
public class TypeName
911+
{
912+
public void Test()
913+
{
914+
Test2(resolve: arg =>
915+
{
916+
return """";
917+
});
918+
}
919+
920+
private void Test2(string description = null, Func<object, string> resolve = null)
921+
{
922+
resolve(0);
923+
}
924+
}";
925+
926+
var expected = new[]
927+
{
928+
Diagnostic().WithSpan(8, 24, 8, 32),
929+
};
930+
931+
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
932+
}
933+
934+
[Fact]
935+
[WorkItem(3279, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3279")]
936+
public async Task VerifyThatUnknownNamedParameterWontCauseCrashAsync()
937+
{
938+
var testCode = @"
939+
using System;
940+
using System.Linq;
941+
public class TypeName
942+
{
943+
public void Test()
944+
{
945+
Test2(unknownParam: delegate
946+
{
947+
return """";
948+
});
949+
}
950+
951+
private void Test2(string description = null, Func<object, string> resolve = null)
952+
{
953+
resolve(0);
954+
}
955+
}";
956+
957+
var expected = DiagnosticResult.CompilerError("CS1739")
958+
.WithMessage("The best overload for 'Test2' does not have a parameter named 'unknownParam'")
959+
.WithSpan(8, 15, 8, 27)
960+
.WithArguments("Test2", "unknownParam");
961+
962+
await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
963+
}
883964
}
884965
}

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1130UseLambdaSyntax.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,27 @@ internal static ParameterListSyntax GetDelegateParameterList(ISymbol symbol, int
9696
return SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(syntaxParameters));
9797
}
9898

99+
internal static int FindParameterIndex(SymbolInfo originalSymbolInfo, ArgumentSyntax argumentSyntax, BaseArgumentListSyntax argumentListSyntax)
100+
{
101+
// if delegate is passed as named argument of method try to find its position by argument name
102+
if (argumentSyntax.NameColon != null
103+
&& originalSymbolInfo.Symbol is IMethodSymbol methodSymbol)
104+
{
105+
var calledMethodParameters = methodSymbol.Parameters;
106+
var argumentIdentifier = argumentSyntax.NameColon.Name.Identifier.ValueText;
107+
108+
for (var i = 0; i < calledMethodParameters.Length; ++i)
109+
{
110+
if (string.Equals(calledMethodParameters[i].Name, argumentIdentifier))
111+
{
112+
return i;
113+
}
114+
}
115+
}
116+
117+
return argumentListSyntax.Arguments.IndexOf(argumentSyntax);
118+
}
119+
99120
private static void HandleAnonymousMethodExpression(SyntaxNodeAnalysisContext context)
100121
{
101122
bool reportDiagnostic = true;
@@ -138,7 +159,7 @@ private static bool HandleMethodInvocation(SemanticModel semanticModel, Anonymou
138159
return false;
139160
}
140161

141-
var argumentIndex = argumentListSyntax.Arguments.IndexOf(argumentSyntax);
162+
var argumentIndex = FindParameterIndex(originalSymbolInfo, argumentSyntax, argumentListSyntax);
142163

143164
// Determine the parameter list from the method that is invoked, as delegates without parameters are allowed, but they cannot be replaced by a lambda without parameters.
144165
var parameterList = GetDelegateParameterList(originalSymbolInfo.Symbol, argumentIndex);

0 commit comments

Comments
 (0)