Skip to content

Commit 36f347e

Browse files
committed
Update SA1502 to handle local functions
1 parent bae8059 commit 36f347e

4 files changed

Lines changed: 265 additions & 8 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1502CodeFixProvider.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ namespace StyleCop.Analyzers.LayoutRules
88
using System.Composition;
99
using System.Threading;
1010
using System.Threading.Tasks;
11-
using Helpers;
1211
using Microsoft.CodeAnalysis;
1312
using Microsoft.CodeAnalysis.CodeActions;
1413
using Microsoft.CodeAnalysis.CodeFixes;
1514
using Microsoft.CodeAnalysis.CSharp;
1615
using Microsoft.CodeAnalysis.CSharp.Syntax;
17-
using Settings.ObjectModel;
16+
using StyleCop.Analyzers.Helpers;
17+
using StyleCop.Analyzers.Lightup;
18+
using StyleCop.Analyzers.Settings.ObjectModel;
1819

1920
/// <summary>
2021
/// Implements a code fix for <see cref="SA1502ElementMustNotBeOnASingleLine"/>.
@@ -77,7 +78,15 @@ private Document CreateCodeFix(Document document, IndentationSettings indentatio
7778
break;
7879

7980
case SyntaxKind.Block:
80-
newSyntaxRoot = this.RegisterMethodLikeDeclarationCodeFix(syntaxRoot, (BaseMethodDeclarationSyntax)node.Parent, indentationSettings);
81+
if (node.Parent.IsKind(SyntaxKindEx.LocalFunctionStatement))
82+
{
83+
newSyntaxRoot = this.RegisterLocalFunctionStatementCodeFix(syntaxRoot, (LocalFunctionStatementSyntaxWrapper)node.Parent, indentationSettings);
84+
}
85+
else
86+
{
87+
newSyntaxRoot = this.RegisterMethodLikeDeclarationCodeFix(syntaxRoot, (BaseMethodDeclarationSyntax)node.Parent, indentationSettings);
88+
}
89+
8190
break;
8291

8392
case SyntaxKind.NamespaceDeclaration:
@@ -103,6 +112,11 @@ private SyntaxNode RegisterMethodLikeDeclarationCodeFix(SyntaxNode syntaxRoot, B
103112
return this.ReformatElement(syntaxRoot, node, node.Body.OpenBraceToken, node.Body.CloseBraceToken, indentationSettings);
104113
}
105114

115+
private SyntaxNode RegisterLocalFunctionStatementCodeFix(SyntaxNode syntaxRoot, LocalFunctionStatementSyntaxWrapper node, IndentationSettings indentationSettings)
116+
{
117+
return this.ReformatElement(syntaxRoot, node, node.Body.OpenBraceToken, node.Body.CloseBraceToken, indentationSettings);
118+
}
119+
106120
private SyntaxNode RegisterEnumDeclarationCodeFix(SyntaxNode syntaxRoot, EnumDeclarationSyntax node, IndentationSettings indentationSettings)
107121
{
108122
return this.ReformatElement(syntaxRoot, node, node.OpenBraceToken, node.CloseBraceToken, indentationSettings);

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/LayoutRules/SA1502CSharp7UnitTests.cs

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,238 @@
33

44
namespace StyleCop.Analyzers.Test.CSharp7.LayoutRules
55
{
6+
using System.Threading;
7+
using System.Threading.Tasks;
68
using StyleCop.Analyzers.Test.LayoutRules;
9+
using Xunit;
710

811
public class SA1502CSharp7UnitTests : SA1502UnitTests
912
{
13+
/// <summary>
14+
/// Verifies that a valid local function will pass without diagnostic.
15+
/// </summary>
16+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
17+
[Fact]
18+
public async Task TestValidEmptyLocalFunctionAsync()
19+
{
20+
var testCode = @"public class TypeName
21+
{
22+
public void Method()
23+
{
24+
void Bar()
25+
{
26+
}
27+
}
28+
}";
29+
30+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
31+
}
32+
33+
/// <summary>
34+
/// Verifies that an empty local function with its block on the same line will trigger a diagnostic.
35+
/// </summary>
36+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
37+
[Fact]
38+
public async Task TestEmptyLocalFunctionOnSingleLineAsync()
39+
{
40+
var testCode = @"public class TypeName
41+
{
42+
public void Method()
43+
{
44+
void Bar() { }
45+
}
46+
}";
47+
48+
var expected = this.CSharpDiagnostic().WithLocation(5, 20);
49+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
50+
}
51+
52+
/// <summary>
53+
/// Verifies that a local function with its block on the same line will trigger a diagnostic.
54+
/// </summary>
55+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
56+
[Fact]
57+
public async Task TestLocalFunctionOnSingleLineAsync()
58+
{
59+
var testCode = @"public class TypeName
60+
{
61+
public void Method()
62+
{
63+
int Bar() { return 0; }
64+
}
65+
}";
66+
67+
var expected = this.CSharpDiagnostic().WithLocation(5, 19);
68+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
69+
}
70+
71+
/// <summary>
72+
/// Verifies that a local function with its block on a single line will trigger a diagnostic.
73+
/// </summary>
74+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
75+
[Fact]
76+
public async Task TestLocalFunctionWithBlockOnSingleLineAsync()
77+
{
78+
var testCode = @"public class TypeName
79+
{
80+
public void Method()
81+
{
82+
int Bar()
83+
{ return 0; }
84+
}
85+
}";
86+
87+
var expected = this.CSharpDiagnostic().WithLocation(6, 9);
88+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
89+
}
90+
91+
/// <summary>
92+
/// Verifies that a local function with its block on multiple lines will pass without diagnostic.
93+
/// </summary>
94+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
95+
[Fact]
96+
public async Task TestLocalFunctionWithBlockStartOnSameLineAsync()
97+
{
98+
var testCode = @"public class TypeName
99+
{
100+
public void Method()
101+
{
102+
int Bar() {
103+
return 0; }
104+
}
105+
}";
106+
107+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
108+
}
109+
110+
/// <summary>
111+
/// Verifies that a local function with an expression body will pass without diagnostic.
112+
/// </summary>
113+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
114+
[Fact]
115+
public async Task TestLocalFunctionWithExpressionBodyAsync()
116+
{
117+
var testCode = @"public class TypeName
118+
{
119+
public void Method()
120+
{
121+
int Bar(int x, int y) => x + y;
122+
}
123+
}";
124+
125+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
126+
}
127+
128+
/// <summary>
129+
/// Verifies that the code fix for an empty local function with its block on the same line will work properly.
130+
/// </summary>
131+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
132+
[Fact]
133+
public async Task TestEmptyLocalFunctionOnSingleLineCodeFixAsync()
134+
{
135+
var testCode = @"public class TypeName
136+
{
137+
public void Method()
138+
{
139+
void Bar() { }
140+
}
141+
}";
142+
var fixedTestCode = @"public class TypeName
143+
{
144+
public void Method()
145+
{
146+
void Bar()
147+
{
148+
}
149+
}
150+
}";
151+
152+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
153+
}
154+
155+
/// <summary>
156+
/// Verifies that the code fix for a local function with its block on the same line will work properly.
157+
/// </summary>
158+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
159+
[Fact]
160+
public async Task TestLocalFunctionOnSingleLineCodeFixAsync()
161+
{
162+
var testCode = @"public class TypeName
163+
{
164+
public void Method()
165+
{
166+
int Bar() { return 0; }
167+
}
168+
}";
169+
var fixedTestCode = @"public class TypeName
170+
{
171+
public void Method()
172+
{
173+
int Bar()
174+
{
175+
return 0;
176+
}
177+
}
178+
}";
179+
180+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
181+
}
182+
183+
/// <summary>
184+
/// Verifies that the code fix for a local function with its block on a single line will work properly.
185+
/// </summary>
186+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
187+
[Fact]
188+
public async Task TestLocalFunctionWithBlockOnSingleLineCodeFixAsync()
189+
{
190+
var testCode = @"public class TypeName
191+
{
192+
public void Method()
193+
{
194+
int Bar()
195+
{ return 0; }
196+
}
197+
}";
198+
var fixedTestCode = @"public class TypeName
199+
{
200+
public void Method()
201+
{
202+
int Bar()
203+
{
204+
return 0;
205+
}
206+
}
207+
}";
208+
209+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
210+
}
211+
212+
/// <summary>
213+
/// Verifies that the code fix for a local function with lots of trivia is working properly.
214+
/// </summary>
215+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
216+
[Fact]
217+
public async Task TestLocalFunctionWithLotsOfTriviaCodeFixAsync()
218+
{
219+
var testCode = @"public class TypeName
220+
{
221+
public void Method()
222+
{
223+
int Bar() /* TR1 */ { /* TR2 */ return 0; /* TR3 */ } /* TR4 */
224+
}
225+
}";
226+
var fixedTestCode = @"public class TypeName
227+
{
228+
public void Method()
229+
{
230+
int Bar() /* TR1 */
231+
{ /* TR2 */
232+
return 0; /* TR3 */
233+
} /* TR4 */
234+
}
235+
}";
236+
237+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
238+
}
10239
}
11240
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1502/SA1502UnitTests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ namespace StyleCop.Analyzers.Test.LayoutRules
2020
/// </remarks>
2121
public partial class SA1502UnitTests : CodeFixVerifier
2222
{
23+
protected static string FormatTestCode(string testCode, string placeHolderReplacement)
24+
{
25+
return testCode.Replace("##PH##", placeHolderReplacement);
26+
}
27+
2328
/// <inheritdoc/>
2429
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
2530
{
@@ -31,10 +36,5 @@ protected override CodeFixProvider GetCSharpCodeFixProvider()
3136
{
3237
return new SA1502CodeFixProvider();
3338
}
34-
35-
private static string FormatTestCode(string testCode, string placeHolderReplacement)
36-
{
37-
return testCode.Replace("##PH##", placeHolderReplacement);
38-
}
3939
}
4040
}

StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1502ElementMustNotBeOnASingleLine.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace StyleCop.Analyzers.LayoutRules
1111
using Microsoft.CodeAnalysis.CSharp.Syntax;
1212
using Microsoft.CodeAnalysis.Diagnostics;
1313
using StyleCop.Analyzers.Helpers;
14+
using StyleCop.Analyzers.Lightup;
1415

1516
/// <summary>
1617
/// A C# element containing opening and closing braces is written completely on a single line.
@@ -54,6 +55,7 @@ internal class SA1502ElementMustNotBeOnASingleLine : DiagnosticAnalyzer
5455
private static readonly Action<SyntaxNodeAnalysisContext> BaseTypeDeclarationAction = HandleBaseTypeDeclaration;
5556
private static readonly Action<SyntaxNodeAnalysisContext> BasePropertyDeclarationAction = HandleBasePropertyDeclaration;
5657
private static readonly Action<SyntaxNodeAnalysisContext> BaseMethodDeclarationAction = HandleBaseMethodDeclaration;
58+
private static readonly Action<SyntaxNodeAnalysisContext> LocalFunctionStatementAction = HandleLocalFunctionStatement;
5759
private static readonly Action<SyntaxNodeAnalysisContext> NamespaceDeclarationAction = HandleNamespaceDeclaration;
5860

5961
/// <inheritdoc/>
@@ -69,6 +71,7 @@ public override void Initialize(AnalysisContext context)
6971
context.RegisterSyntaxNodeAction(BaseTypeDeclarationAction, SyntaxKinds.BaseTypeDeclaration);
7072
context.RegisterSyntaxNodeAction(BasePropertyDeclarationAction, SyntaxKinds.BasePropertyDeclaration);
7173
context.RegisterSyntaxNodeAction(BaseMethodDeclarationAction, SyntaxKinds.BaseMethodDeclaration);
74+
context.RegisterSyntaxNodeAction(LocalFunctionStatementAction, SyntaxKindEx.LocalFunctionStatement);
7275
context.RegisterSyntaxNodeAction(NamespaceDeclarationAction, SyntaxKind.NamespaceDeclaration);
7376
}
7477

@@ -104,6 +107,17 @@ private static void HandleBaseMethodDeclaration(SyntaxNodeAnalysisContext contex
104107
}
105108
}
106109

110+
private static void HandleLocalFunctionStatement(SyntaxNodeAnalysisContext context)
111+
{
112+
var localFunctionStatement = (LocalFunctionStatementSyntaxWrapper)context.Node;
113+
114+
// Expression-bodied local functions do not have a body
115+
if (localFunctionStatement.Body != null)
116+
{
117+
CheckViolation(context, localFunctionStatement.Body.OpenBraceToken, localFunctionStatement.Body.CloseBraceToken);
118+
}
119+
}
120+
107121
private static void HandleNamespaceDeclaration(SyntaxNodeAnalysisContext context)
108122
{
109123
var namespaceDeclaration = (NamespaceDeclarationSyntax)context.Node;

0 commit comments

Comments
 (0)