Skip to content

Commit 04e9a1a

Browse files
authored
Merge pull request #4036 from sharwell/static-local-function
Support static local functions from C# 8
2 parents 7e54922 + ad259e5 commit 04e9a1a

File tree

8 files changed

+219
-38
lines changed

8 files changed

+219
-38
lines changed

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/SA1206CodeFixProvider.cs

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ namespace StyleCop.Analyzers.OrderingRules
1414
using Microsoft.CodeAnalysis;
1515
using Microsoft.CodeAnalysis.CodeActions;
1616
using Microsoft.CodeAnalysis.CodeFixes;
17+
using Microsoft.CodeAnalysis.CSharp;
1718
using Microsoft.CodeAnalysis.CSharp.Syntax;
1819
using StyleCop.Analyzers.Helpers;
20+
using StyleCop.Analyzers.Lightup;
1921
using static StyleCop.Analyzers.OrderingRules.ModifierOrderHelper;
2022

2123
/// <summary>
@@ -55,31 +57,45 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen
5557
{
5658
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
5759

58-
var memberDeclaration = syntaxRoot.FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf<MemberDeclarationSyntax>();
59-
if (memberDeclaration == null)
60+
var memberOrLocalFunction = syntaxRoot.FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf<CSharpSyntaxNode>(static node => node is MemberDeclarationSyntax || LocalFunctionStatementSyntaxWrapper.IsInstance(node));
61+
if (memberOrLocalFunction == null)
6062
{
6163
return document;
6264
}
6365

64-
var modifierTokenToFix = memberDeclaration.FindToken(diagnostic.Location.SourceSpan.Start);
66+
var modifierTokenToFix = memberOrLocalFunction.FindToken(diagnostic.Location.SourceSpan.Start);
6567
if (GetModifierType(modifierTokenToFix) == ModifierType.None)
6668
{
6769
return document;
6870
}
6971

70-
var newModifierList = PartiallySortModifiers(memberDeclaration.GetModifiers(), modifierTokenToFix);
71-
syntaxRoot = UpdateSyntaxRoot(memberDeclaration, newModifierList, syntaxRoot);
72+
if (memberOrLocalFunction is MemberDeclarationSyntax memberDeclaration)
73+
{
74+
var newModifierList = PartiallySortModifiers(memberDeclaration.GetModifiers(), modifierTokenToFix);
75+
syntaxRoot = UpdateSyntaxRoot(memberDeclaration, newModifierList, syntaxRoot);
76+
}
77+
else
78+
{
79+
var localFunctionStatement = (LocalFunctionStatementSyntaxWrapper)memberOrLocalFunction;
80+
var newModifierList = PartiallySortModifiers(localFunctionStatement.Modifiers, modifierTokenToFix);
81+
syntaxRoot = UpdateSyntaxRoot(localFunctionStatement, newModifierList, syntaxRoot);
82+
}
7283

7384
return document.WithSyntaxRoot(syntaxRoot);
7485
}
7586

7687
private static SyntaxNode UpdateSyntaxRoot(MemberDeclarationSyntax memberDeclaration, SyntaxTokenList newModifiers, SyntaxNode syntaxRoot)
7788
{
78-
var newDeclaration = memberDeclaration.WithModifiers(newModifiers);
79-
89+
var newDeclaration = DeclarationModifiersHelper.WithModifiers(memberDeclaration, newModifiers);
8090
return syntaxRoot.ReplaceNode(memberDeclaration, newDeclaration);
8191
}
8292

93+
private static SyntaxNode UpdateSyntaxRoot(LocalFunctionStatementSyntaxWrapper localFunctionStatement, SyntaxTokenList newModifiers, SyntaxNode syntaxRoot)
94+
{
95+
var newDeclaration = localFunctionStatement.WithModifiers(newModifiers);
96+
return syntaxRoot.ReplaceNode(localFunctionStatement, newDeclaration);
97+
}
98+
8399
/// <summary>
84100
/// Sorts the complete modifier list to fix all issues.
85101
/// The trivia will be maintained positionally.
@@ -175,31 +191,40 @@ protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fi
175191

176192
// because all modifiers can be fixed in one run, we
177193
// only need to store each declaration once
178-
var trackedDiagnosticMembers = new HashSet<MemberDeclarationSyntax>();
194+
var trackedDiagnosticMembers = new HashSet<CSharpSyntaxNode>();
179195
foreach (var diagnostic in diagnostics)
180196
{
181-
var memberDeclaration = syntaxRoot.FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf<MemberDeclarationSyntax>();
182-
if (memberDeclaration == null)
197+
var memberOrLocalFunction = syntaxRoot.FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf<CSharpSyntaxNode>(static node => node is MemberDeclarationSyntax || LocalFunctionStatementSyntaxWrapper.IsInstance(node));
198+
if (memberOrLocalFunction == null)
183199
{
184200
continue;
185201
}
186202

187-
var modifierToken = memberDeclaration.FindToken(diagnostic.Location.SourceSpan.Start);
203+
var modifierToken = memberOrLocalFunction.FindToken(diagnostic.Location.SourceSpan.Start);
188204
if (GetModifierType(modifierToken) == ModifierType.None)
189205
{
190206
continue;
191207
}
192208

193-
trackedDiagnosticMembers.Add(memberDeclaration);
209+
trackedDiagnosticMembers.Add(memberOrLocalFunction);
194210
}
195211

196212
syntaxRoot = syntaxRoot.TrackNodes(trackedDiagnosticMembers);
197213

198214
foreach (var member in trackedDiagnosticMembers)
199215
{
200-
var memberDeclaration = syntaxRoot.GetCurrentNode(member);
201-
var newModifierList = FullySortModifiers(memberDeclaration.GetModifiers());
202-
syntaxRoot = UpdateSyntaxRoot(memberDeclaration, newModifierList, syntaxRoot);
216+
var currentMember = syntaxRoot.GetCurrentNode(member);
217+
if (currentMember is MemberDeclarationSyntax memberDeclaration)
218+
{
219+
var newModifierList = FullySortModifiers(memberDeclaration.GetModifiers());
220+
syntaxRoot = UpdateSyntaxRoot(memberDeclaration, newModifierList, syntaxRoot);
221+
}
222+
else
223+
{
224+
var localFunctionStatement = (LocalFunctionStatementSyntaxWrapper)currentMember;
225+
var newModifierList = FullySortModifiers(localFunctionStatement.Modifiers);
226+
syntaxRoot = UpdateSyntaxRoot(localFunctionStatement, newModifierList, syntaxRoot);
227+
}
203228
}
204229

205230
return syntaxRoot;

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/NamingRules/SA1300CSharp7UnitTests.cs

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
22
// Licensed under the MIT License. See LICENSE in the project root for license information.
33

4-
#nullable disable
5-
64
namespace StyleCop.Analyzers.Test.CSharp7.NamingRules
75
{
86
using System.Threading;
@@ -16,30 +14,14 @@ namespace StyleCop.Analyzers.Test.CSharp7.NamingRules
1614

1715
public partial class SA1300CSharp7UnitTests : SA1300UnitTests
1816
{
19-
[Fact]
20-
public async Task TestUpperCaseLocalFunctionAsync()
21-
{
22-
var testCode = @"public class TestClass
23-
{
24-
public void Method()
25-
{
26-
void LocalFunction()
27-
{
28-
}
29-
}
30-
}";
31-
32-
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
33-
}
34-
3517
[Fact]
3618
public async Task TestLowerCaseLocalFunctionAsync()
3719
{
3820
var testCode = @"public class TestClass
3921
{
4022
public void Method()
4123
{
42-
void localFunction()
24+
void {|#0:localFunction|}()
4325
{
4426
}
4527
}
@@ -54,7 +36,7 @@ void LocalFunction()
5436
}
5537
}";
5638

57-
DiagnosticResult expected = Diagnostic().WithArguments("localFunction").WithLocation(5, 14);
39+
DiagnosticResult expected = Diagnostic().WithArguments("localFunction").WithLocation(0);
5840
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
5941
}
6042

@@ -66,7 +48,7 @@ public async Task TestLowerCaseLocalFunctionWithConflictAsync()
6648
{
6749
public void Method()
6850
{
69-
void localFunction()
51+
void {|#0:localFunction|}()
7052
{
7153
}
7254
@@ -85,7 +67,7 @@ void LocalFunction1()
8567
}
8668
}";
8769

88-
DiagnosticResult expected = Diagnostic().WithArguments("localFunction").WithLocation(5, 14);
70+
DiagnosticResult expected = Diagnostic().WithArguments("localFunction").WithLocation(0);
8971
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
9072
}
9173
}

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/ReadabilityRules/SA1101CSharp7UnitTests.cs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,18 @@ public class TestClass
121121
{
122122
private int foobar = 1;
123123
124+
public int Foo()
125+
{
126+
int Quux<T>() => {|#0:foobar|};
127+
return Quux<int>();
128+
}
129+
}
130+
";
131+
var fixedCode = @"
132+
public class TestClass
133+
{
134+
private int foobar = 1;
135+
124136
public int Foo()
125137
{
126138
int Quux<T>() => this.foobar;
@@ -129,7 +141,46 @@ public int Foo()
129141
}
130142
";
131143

132-
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
144+
DiagnosticResult[] expected =
145+
{
146+
Diagnostic().WithLocation(0),
147+
};
148+
149+
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
150+
}
151+
152+
[Fact]
153+
[WorkItem(3005, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3005")]
154+
public async Task TestLocalFunctionRequiresThisAsync()
155+
{
156+
var testCode = @"public class TestClass
157+
{
158+
private int field;
159+
160+
public int Method()
161+
{
162+
int Local() => {|#0:field|};
163+
return Local();
164+
}
165+
}";
166+
167+
var fixedCode = @"public class TestClass
168+
{
169+
private int field;
170+
171+
public int Method()
172+
{
173+
int Local() => this.field;
174+
return Local();
175+
}
176+
}";
177+
178+
DiagnosticResult[] expected =
179+
{
180+
Diagnostic().WithLocation(0),
181+
};
182+
183+
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
133184
}
134185
}
135186
}

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1300CSharp8UnitTests.cs

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

44
namespace StyleCop.Analyzers.Test.CSharp8.NamingRules
55
{
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.CodeAnalysis.Testing;
69
using StyleCop.Analyzers.Test.CSharp7.NamingRules;
10+
using Xunit;
11+
using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier<
12+
StyleCop.Analyzers.NamingRules.SA1300ElementMustBeginWithUpperCaseLetter,
13+
StyleCop.Analyzers.NamingRules.RenameToUpperCaseCodeFixProvider>;
714

815
public partial class SA1300CSharp8UnitTests : SA1300CSharp7UnitTests
916
{
17+
[Fact]
18+
[WorkItem(3005, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3005")]
19+
public async Task TestLowerCaseStaticLocalFunctionAsync()
20+
{
21+
var testCode = @"public class TestClass
22+
{
23+
public void Method()
24+
{
25+
static void {|#0:localFunction|}()
26+
{
27+
}
28+
}
29+
}";
30+
var fixedCode = @"public class TestClass
31+
{
32+
public void Method()
33+
{
34+
static void LocalFunction()
35+
{
36+
}
37+
}
38+
}";
39+
40+
DiagnosticResult expected = Diagnostic().WithArguments("localFunction").WithLocation(0);
41+
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
42+
}
1043
}
1144
}

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1206CSharp8UnitTests.cs

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

44
namespace StyleCop.Analyzers.Test.CSharp8.OrderingRules
55
{
6+
using System.Threading;
7+
using System.Threading.Tasks;
68
using StyleCop.Analyzers.Test.CSharp7.OrderingRules;
9+
using Xunit;
10+
using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier<
11+
StyleCop.Analyzers.OrderingRules.SA1206DeclarationKeywordsMustFollowOrder,
12+
StyleCop.Analyzers.OrderingRules.SA1206CodeFixProvider>;
713

814
public partial class SA1206CSharp8UnitTests : SA1206CSharp7UnitTests
915
{
16+
[Fact]
17+
[WorkItem(3005, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3005")]
18+
public async Task TestStaticLocalFunctionIncorrectOrderAsync()
19+
{
20+
var testCode = @"public class TestClass
21+
{
22+
public void Method()
23+
{
24+
async {|#0:static|} void Local()
25+
{
26+
}
27+
}
28+
}";
29+
var fixedCode = @"public class TestClass
30+
{
31+
public void Method()
32+
{
33+
static async void Local()
34+
{
35+
}
36+
}
37+
}";
38+
39+
var expected = Diagnostic().WithArguments("static", "async").WithLocation(0);
40+
41+
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
42+
}
43+
44+
[Fact]
45+
[WorkItem(3005, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3005")]
46+
public async Task TestStaticUnsafeLocalFunctionIncorrectOrderAsync()
47+
{
48+
var testCode = @"public class TestClass
49+
{
50+
public void Method()
51+
{
52+
unsafe {|#0:static|} void Local()
53+
{
54+
}
55+
}
56+
}";
57+
var fixedCode = @"public class TestClass
58+
{
59+
public void Method()
60+
{
61+
static unsafe void Local()
62+
{
63+
}
64+
}
65+
}";
66+
67+
var expected = Diagnostic().WithArguments("static", "unsafe").WithLocation(0);
68+
69+
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
70+
}
1071
}
1172
}

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1101CSharp8UnitTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,25 @@ namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules
1414

1515
public partial class SA1101CSharp8UnitTests : SA1101CSharp7UnitTests
1616
{
17+
[Fact]
18+
[WorkItem(3005, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3005")]
19+
public async Task TestStaticLocalFunctionCallAsync()
20+
{
21+
var testCode = @"public class TestClass
22+
{
23+
public void Method()
24+
{
25+
static void LocalFunction()
26+
{
27+
}
28+
29+
LocalFunction();
30+
}
31+
}";
32+
33+
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
34+
}
35+
1736
[Fact]
1837
[WorkItem(3472, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3472")]
1938
public async Task TestPropertyPatternAsync()

0 commit comments

Comments
 (0)