Skip to content

Commit ad259e5

Browse files
committed
Update SA1206 for static local functions
1 parent 2fe904f commit ad259e5

File tree

3 files changed

+110
-15
lines changed

3 files changed

+110
-15
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.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/OrderingRules/SA1206DeclarationKeywordsMustFollowOrder.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace StyleCop.Analyzers.OrderingRules
1212
using Microsoft.CodeAnalysis.CSharp.Syntax;
1313
using Microsoft.CodeAnalysis.Diagnostics;
1414
using StyleCop.Analyzers.Helpers;
15+
using StyleCop.Analyzers.Lightup;
1516
using static ModifierOrderHelper;
1617

1718
/// <summary>
@@ -66,6 +67,7 @@ internal class SA1206DeclarationKeywordsMustFollowOrder : DiagnosticAnalyzer
6667
SyntaxKind.ConstructorDeclaration);
6768

6869
private static readonly Action<SyntaxNodeAnalysisContext> DeclarationAction = HandleDeclaration;
70+
private static readonly Action<SyntaxNodeAnalysisContext> LocalFunctionStatementAction = HandleLocalFunctionStatement;
6971

7072
/// <inheritdoc/>
7173
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
@@ -78,6 +80,7 @@ public override void Initialize(AnalysisContext context)
7880
context.EnableConcurrentExecution();
7981

8082
context.RegisterSyntaxNodeAction(DeclarationAction, HandledSyntaxKinds);
83+
context.RegisterSyntaxNodeAction(LocalFunctionStatementAction, SyntaxKindEx.LocalFunctionStatement);
8184
}
8285

8386
private static void HandleDeclaration(SyntaxNodeAnalysisContext context)
@@ -86,6 +89,12 @@ private static void HandleDeclaration(SyntaxNodeAnalysisContext context)
8689
CheckModifiersOrderAndReportDiagnostics(context, modifiers);
8790
}
8891

92+
private static void HandleLocalFunctionStatement(SyntaxNodeAnalysisContext context)
93+
{
94+
var localFunction = (LocalFunctionStatementSyntaxWrapper)context.Node;
95+
CheckModifiersOrderAndReportDiagnostics(context, localFunction.Modifiers);
96+
}
97+
8998
private static void CheckModifiersOrderAndReportDiagnostics(SyntaxNodeAnalysisContext context, SyntaxTokenList modifiers)
9099
{
91100
var previousModifierType = ModifierType.None;

0 commit comments

Comments
 (0)