Skip to content

Commit dac9f93

Browse files
committed
Merge pull request #1510 from pdelvo/SA1130
2 parents 2b5b96a + a7a3462 commit dac9f93

5 files changed

Lines changed: 169 additions & 4 deletions

File tree

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.Test.ReadabilityRules
5+
{
6+
using System.Collections.Generic;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Analyzers.ReadabilityRules;
10+
using Microsoft.CodeAnalysis.Diagnostics;
11+
using TestHelper;
12+
using Xunit;
13+
14+
public class SA1130UnitTests : DiagnosticVerifier
15+
{
16+
[Fact]
17+
public async Task TestSimpleDelegateUseAsync()
18+
{
19+
var testCode = @"
20+
using System;
21+
public class TypeName
22+
{
23+
public void Test()
24+
{
25+
Action action1 = delegate { };
26+
Action action2 = delegate() { };
27+
Action<int> action3 = delegate(int i) { };
28+
}
29+
}";
30+
var expected = new[]
31+
{
32+
this.CSharpDiagnostic().WithLocation(7, 26),
33+
this.CSharpDiagnostic().WithLocation(8, 26),
34+
this.CSharpDiagnostic().WithLocation(9, 31)
35+
};
36+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
37+
}
38+
39+
[Fact]
40+
public async Task TestDelegateUseAsMethodArgumentsAsync()
41+
{
42+
var testCode = @"
43+
using System;
44+
public class TypeName
45+
{
46+
public void Test(Action argument)
47+
{
48+
49+
}
50+
51+
public void Test()
52+
{
53+
Test(delegate { });
54+
}
55+
}";
56+
var expected = this.CSharpDiagnostic().WithLocation(12, 14);
57+
58+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
59+
}
60+
61+
[Fact]
62+
public async Task TestDelegateUseAsMethodArgumentsWithConflictingExpressionOverloadAsync()
63+
{
64+
var testCode = @"
65+
using System;
66+
using System.Linq.Expressions;
67+
public class TypeName
68+
{
69+
public void Test(Action argument)
70+
{
71+
72+
}
73+
74+
public void Test(Expression<Action> argument)
75+
{
76+
77+
}
78+
79+
public void Test()
80+
{
81+
Test(delegate { });
82+
}
83+
}";
84+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
85+
}
86+
87+
[Fact]
88+
public async Task TestDelegateUseAsMethodArgumentsWithNonConflictingExpressionOverloadAsync()
89+
{
90+
var testCode = @"
91+
using System;
92+
using System.Linq.Expressions;
93+
public class TypeName
94+
{
95+
public void Test(Action argument)
96+
{
97+
98+
}
99+
100+
public void Test(Expression<Func<int>> argument)
101+
{
102+
103+
}
104+
105+
public void Test()
106+
{
107+
Test(delegate { });
108+
}
109+
}";
110+
var expected = this.CSharpDiagnostic().WithLocation(18, 14);
111+
112+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
113+
}
114+
115+
/// <inheritdoc/>
116+
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
117+
{
118+
yield return new SA1130UseLambdaSyntax();
119+
}
120+
}
121+
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@
303303
<Compile Include="ReadabilityRules\SA1126UnitTests.cs" />
304304
<Compile Include="ReadabilityRules\SA1127UnitTests.cs" />
305305
<Compile Include="ReadabilityRules\SA1128UnitTests.cs" />
306+
<Compile Include="ReadabilityRules\SA1130UnitTests.cs" />
306307
<Compile Include="ReadabilityRules\SA1132UnitTests.cs" />
307308
<Compile Include="Settings\SettingsFileCodeFixProviderUnitTests.cs" />
308309
<Compile Include="Settings\SettingsUnitTests.cs" />

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/ReadabilityResources.Designer.cs

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/ReadabilityResources.resx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@
424424
<value>Lambda expressions are more succinct and easier to read than anonymous methods, so they should are preferred whenever the two are functionally equivalent.</value>
425425
</data>
426426
<data name="SA1130MessageFormat" xml:space="preserve">
427-
<value />
427+
<value>Use lambda syntax</value>
428428
</data>
429429
<data name="SA1130Title" xml:space="preserve">
430430
<value>Use lambda syntax</value>

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

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ namespace StyleCop.Analyzers.ReadabilityRules
55
{
66
using System.Collections.Immutable;
77
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.CSharp;
9+
using Microsoft.CodeAnalysis.CSharp.Syntax;
810
using Microsoft.CodeAnalysis.Diagnostics;
911

1012
/// <summary>
@@ -22,9 +24,10 @@ internal class SA1130UseLambdaSyntax : DiagnosticAnalyzer
2224
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(ReadabilityResources.SA1130MessageFormat), ReadabilityResources.ResourceManager, typeof(ReadabilityResources));
2325
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(ReadabilityResources.SA1130Description), ReadabilityResources.ResourceManager, typeof(ReadabilityResources));
2426
private static readonly string HelpLink = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1130.md";
27+
private static readonly ParameterListSyntax EmptyParameterList = SyntaxFactory.ParameterList();
2528

2629
private static readonly DiagnosticDescriptor Descriptor =
27-
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.ReadabilityRules, DiagnosticSeverity.Warning, AnalyzerConstants.DisabledNoTests, Description, HelpLink);
30+
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.ReadabilityRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink);
2831

2932
/// <inheritdoc/>
3033
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
@@ -33,7 +36,47 @@ internal class SA1130UseLambdaSyntax : DiagnosticAnalyzer
3336
/// <inheritdoc/>
3437
public override void Initialize(AnalysisContext context)
3538
{
36-
// TODO: Implement analysis
39+
context.RegisterCompilationStartAction(HandleCompilationStart);
40+
}
41+
42+
private static void HandleCompilationStart(CompilationStartAnalysisContext context)
43+
{
44+
context.RegisterSyntaxNodeActionHonorExclusions(HandleAnonymousMethodExpression, SyntaxKind.AnonymousMethodExpression);
45+
}
46+
47+
private static void HandleAnonymousMethodExpression(SyntaxNodeAnalysisContext context)
48+
{
49+
var anonymousMethod = (AnonymousMethodExpressionSyntax)context.Node;
50+
51+
if (anonymousMethod.Parent.IsKind(SyntaxKind.Argument))
52+
{
53+
// invocation -> argument list -> argument -> anonymous method
54+
var originalInvocationExpression = anonymousMethod?.Parent?.Parent?.Parent as InvocationExpressionSyntax;
55+
56+
if (originalInvocationExpression != null)
57+
{
58+
// In some cases passing a delegate as an argument to a method is required to call the right overload
59+
// When there is an other overload that takes an expression.
60+
var lambdaExpression = SyntaxFactory.ParenthesizedLambdaExpression(
61+
anonymousMethod.AsyncKeyword,
62+
anonymousMethod.ParameterList ?? EmptyParameterList,
63+
SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken),
64+
anonymousMethod.Body);
65+
66+
var invocationExpression = originalInvocationExpression.ReplaceNode(anonymousMethod, lambdaExpression);
67+
68+
SymbolInfo originalSymbolInfo = context.SemanticModel.GetSymbolInfo(originalInvocationExpression);
69+
Location location = originalInvocationExpression.GetLocation();
70+
SymbolInfo newSymbolInfo = context.SemanticModel.GetSpeculativeSymbolInfo(location.SourceSpan.Start, invocationExpression, SpeculativeBindingOption.BindAsExpression);
71+
72+
if (originalSymbolInfo.Symbol != newSymbolInfo.Symbol)
73+
{
74+
return;
75+
}
76+
}
77+
}
78+
79+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, anonymousMethod.GetLocation()));
3780
}
3881
}
3982
}

0 commit comments

Comments
 (0)