diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1130CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1130CodeFixProvider.cs index 9d66d8cc4..60b6ed3b2 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1130CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1130CodeFixProvider.cs @@ -72,6 +72,7 @@ private static SyntaxNode ReplaceWithLambda(SemanticModel semanticModel, Anonymo { var parameterList = anonymousMethod.ParameterList; SyntaxNode lambdaExpression; + SyntaxToken arrowToken; if (parameterList == null) { @@ -98,6 +99,16 @@ private static SyntaxNode ReplaceWithLambda(SemanticModel semanticModel, Anonymo argumentList = list.Value; break; + + case SyntaxKind.ArrowExpressionClause: + case SyntaxKind.ReturnStatement: + argumentList = GetMemberReturnTypeArgumentList(semanticModel, anonymousMethod); + if (argumentList.IsEmpty) + { + return null; + } + + break; } List parameters = GenerateUniqueParameterNames(semanticModel, anonymousMethod, argumentList); @@ -107,12 +118,17 @@ private static SyntaxNode ReplaceWithLambda(SemanticModel semanticModel, Anonymo : SyntaxFactory.SeparatedList(); parameterList = SyntaxFactory.ParameterList(newList) - .WithLeadingTrivia(anonymousMethod.DelegateKeyword.LeadingTrivia) + .WithLeadingTrivia(anonymousMethod.DelegateKeyword.LeadingTrivia); + + arrowToken = SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken) .WithTrailingTrivia(anonymousMethod.DelegateKeyword.TrailingTrivia); } else { parameterList = parameterList.WithLeadingTrivia(anonymousMethod.DelegateKeyword.TrailingTrivia); + + arrowToken = SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken) + .WithTrailingTrivia(SyntaxFactory.ElasticSpace); } foreach (var parameter in parameterList.Parameters) @@ -123,9 +139,6 @@ private static SyntaxNode ReplaceWithLambda(SemanticModel semanticModel, Anonymo } } - var arrowToken = SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken) - .WithTrailingTrivia(SyntaxFactory.ElasticSpace); - if (parameterList.Parameters.Count == 1) { var parameterSyntax = RemoveType(parameterList.Parameters[0]); @@ -193,6 +206,13 @@ private static ImmutableArray GetEqualsArgumentList(SemanticModel semant return namedTypeSymbol.DelegateInvokeMethod.Parameters.Select(ps => ps.Name).ToImmutableArray(); } + private static ImmutableArray GetMemberReturnTypeArgumentList(SemanticModel semanticModel, AnonymousMethodExpressionSyntax anonymousMethod) + { + var enclosingSymbol = semanticModel.GetEnclosingSymbol(anonymousMethod.Parent.SpanStart); + var returnType = ((IMethodSymbol)enclosingSymbol).ReturnType as INamedTypeSymbol; + return (returnType == null) ? ImmutableArray.Empty : returnType.DelegateInvokeMethod.Parameters.Select(ps => ps.Name).ToImmutableArray(); + } + private static List GenerateUniqueParameterNames(SemanticModel semanticModel, AnonymousMethodExpressionSyntax anonymousMethod, ImmutableArray argumentNames) { var parameters = new List(); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/ReadabilityRules/SA1130CSharp7UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/ReadabilityRules/SA1130CSharp7UnitTests.cs index 880861ffd..b99e21eb9 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/ReadabilityRules/SA1130CSharp7UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/ReadabilityRules/SA1130CSharp7UnitTests.cs @@ -3,9 +3,47 @@ namespace StyleCop.Analyzers.Test.CSharp7.ReadabilityRules { + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.Test.ReadabilityRules; + using Xunit; + using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1130UseLambdaSyntax, + StyleCop.Analyzers.ReadabilityRules.SA1130CodeFixProvider>; public class SA1130CSharp7UnitTests : SA1130UnitTests { + [Fact] + [WorkItem(2902, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2902")] + public async Task VerifyLocalFunctionAsync() + { + var testCode = @"using System; +public class TestClass +{ + public void TestMethod() + { + EventHandler LocalTestFunction() => delegate { }; + } +} +"; + + var fixedCode = @"using System; +public class TestClass +{ + public void TestMethod() + { + EventHandler LocalTestFunction() => (sender, e) => { }; + } +} +"; + + DiagnosticResult[] expected = + { + Diagnostic().WithLocation(6, 45), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1130UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1130UnitTests.cs index d43b84ba3..eaae1cbc5 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1130UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1130UnitTests.cs @@ -276,7 +276,7 @@ public class TypeName { public void Test() { - Action action1 = /*a*/()/*b*/ => { }; + Action action1 = /*a*/() =>/*b*/{ }; Action action2 = /*a*//*b*/(/*c*/)/*d*/ => { }; Action action3 = /*a*//*b*//*c*//*d*/i/*e*//*f*/ => { }; Action> action4 = i => { }; @@ -710,5 +710,135 @@ static void Main(string[] args) await VerifyCSharpFixAsync(testCode, expected, testCode, CancellationToken.None).ConfigureAwait(false); } + + [Fact] + [WorkItem(2902, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2902")] + public async Task VerifyThatCodeFixDoesNotCrashOnDelegateReturnAsync() + { + var testCode = @"using System; +public class TestClass +{ + public static EventHandler TestMethod1() + { + return delegate + { + }; + } + + public static EventHandler TestMethod2() => delegate + { + }; + + public static EventHandler TestProperty1 + { + get + { + return delegate + { + }; + } + } + + public static EventHandler TestProperty2 => delegate + { + }; +}"; + + var fixedCode = @"using System; +public class TestClass +{ + public static EventHandler TestMethod1() + { + return (sender, e) => + { + }; + } + + public static EventHandler TestMethod2() => (sender, e) => + { + }; + + public static EventHandler TestProperty1 + { + get + { + return (sender, e) => + { + }; + } + } + + public static EventHandler TestProperty2 => (sender, e) => + { + }; +}"; + + DiagnosticResult[] expected = + { + Diagnostic().WithLocation(6, 16), + Diagnostic().WithLocation(11, 49), + Diagnostic().WithLocation(19, 20), + Diagnostic().WithLocation(25, 49), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + [WorkItem(2902, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2902")] + public async Task VerifyThatEventInitializersWorkAsExpectedAsync() + { + var testCode = @"using System; +public class TestClass +{ + public static event EventHandler StaticEvent = delegate { }; + public event EventHandler InstanceEvent = delegate { }; +} +"; + + var fixedCode = @"using System; +public class TestClass +{ + public static event EventHandler StaticEvent = (sender, e) => { }; + public event EventHandler InstanceEvent = (sender, e) => { }; +} +"; + + DiagnosticResult[] expected = + { + Diagnostic().WithLocation(4, 52), + Diagnostic().WithLocation(5, 47), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + [WorkItem(2902, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2902")] + public async Task VerifyInvalidCodeConstructionsAsync() + { + var testCode = @"using System; +public class TestClass +{ + public static EventHandler[] TestMethod() => delegate { }; +} +"; + + DiagnosticResult[] expected = + { + Diagnostic().WithSpan(4, 50, 4, 58), + DiagnosticResult.CompilerError("CS1660").WithMessage("Cannot convert anonymous method to type 'EventHandler[]' because it is not a delegate type").WithSpan(4, 50, 4, 62), + }; + + var test = new CSharpTest + { + TestCode = testCode, + FixedCode = testCode, + }; + + test.ExpectedDiagnostics.AddRange(expected); + test.RemainingDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None).ConfigureAwait(false); + } } }