Skip to content

Commit ede601c

Browse files
committed
Merge pull request #1705 from bexxx/sa1206-codefixprovider
Added code fix provider for SA1206 and tests.
2 parents 0d21757 + c5d7dac commit ede601c

10 files changed

Lines changed: 679 additions & 99 deletions

File tree

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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.OrderingRules
5+
{
6+
using System.Collections.Generic;
7+
using System.Collections.Immutable;
8+
using System.Composition;
9+
using System.Linq;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
using Microsoft.CodeAnalysis;
13+
using Microsoft.CodeAnalysis.CodeActions;
14+
using Microsoft.CodeAnalysis.CodeFixes;
15+
using Microsoft.CodeAnalysis.CSharp.Syntax;
16+
using StyleCop.Analyzers.Helpers;
17+
using static StyleCop.Analyzers.OrderingRules.ModifierOrderHelper;
18+
19+
/// <summary>
20+
/// Implements code fixes for element ordering rules.
21+
/// </summary>
22+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1206CodeFixProvider))]
23+
[Shared]
24+
internal sealed class SA1206CodeFixProvider : CodeFixProvider
25+
{
26+
/// <inheritdoc/>
27+
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
28+
ImmutableArray.Create(SA1206DeclarationKeywordsMustFollowOrder.DiagnosticId);
29+
30+
/// <inheritdoc/>
31+
public override FixAllProvider GetFixAllProvider()
32+
{
33+
return FixAll.Instance;
34+
}
35+
36+
/// <inheritdoc/>
37+
public override Task RegisterCodeFixesAsync(CodeFixContext context)
38+
{
39+
foreach (Diagnostic diagnostic in context.Diagnostics)
40+
{
41+
context.RegisterCodeFix(
42+
CodeAction.Create(
43+
OrderingResources.ModifierOrderCodeFix,
44+
cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, cancellationToken),
45+
nameof(SA1206CodeFixProvider)),
46+
diagnostic);
47+
}
48+
49+
return SpecializedTasks.CompletedTask;
50+
}
51+
52+
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
53+
{
54+
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
55+
56+
var memberDeclaration = syntaxRoot.FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf<MemberDeclarationSyntax>();
57+
if (memberDeclaration == null)
58+
{
59+
return document;
60+
}
61+
62+
var modifierTokenToFix = memberDeclaration.FindToken(diagnostic.Location.SourceSpan.Start);
63+
if (GetModifierType(modifierTokenToFix) == ModifierType.None)
64+
{
65+
return document;
66+
}
67+
68+
var newModifierList = PartiallySortModifiers(memberDeclaration.GetModifiers(), modifierTokenToFix);
69+
syntaxRoot = UpdateSyntaxRoot(memberDeclaration, newModifierList, syntaxRoot);
70+
71+
return document.WithSyntaxRoot(syntaxRoot);
72+
}
73+
74+
private static SyntaxNode UpdateSyntaxRoot(MemberDeclarationSyntax memberDeclaration, SyntaxTokenList newModifiers, SyntaxNode syntaxRoot)
75+
{
76+
var newDeclaration = memberDeclaration.WithModifiers(newModifiers);
77+
78+
return syntaxRoot.ReplaceNode(memberDeclaration, newDeclaration);
79+
}
80+
81+
/// <summary>
82+
/// Sorts the complete modifier list to fix all issues.
83+
/// The trivia will be maintained positionally.
84+
/// The relative order within the different kinds <seealso cref="ModifierType"/> will not be
85+
/// changed.
86+
/// </summary>
87+
/// <param name="modifiers">All modifiers from the declaration.</param>
88+
/// <returns>A fully sorted modifier list.</returns>
89+
private static SyntaxTokenList FullySortModifiers(SyntaxTokenList modifiers)
90+
{
91+
var accessModifiers = modifiers.Where(modifier => GetModifierType(modifier) == ModifierType.Access);
92+
var staticModifiers = modifiers.Where(modifier => GetModifierType(modifier) == ModifierType.Static);
93+
var otherModifiers = modifiers.Where(modifier => GetModifierType(modifier) == ModifierType.Other);
94+
95+
return AdjustTrivia(
96+
accessModifiers
97+
.Concat(staticModifiers)
98+
.Concat(otherModifiers), modifiers);
99+
}
100+
101+
/// <summary>
102+
/// Sorts the modifier list to fix all issues before <paramref name="modifierToFix"/>
103+
/// and keep the remaining modifiers untouched.
104+
/// The trivia will be maintained positionally.
105+
/// The relative order within the different kinds <seealso cref="ModifierType"/> will not be
106+
/// changed.
107+
/// </summary>
108+
/// <param name="modifiers">All modifiers from the declaration.</param>
109+
/// <param name="modifierToFix">The modifier with diagnostics.</param>
110+
/// <returns>A partially sorted modifier list (sorted up to <paramref name="modifierToFix"/>)</returns>
111+
private static SyntaxTokenList PartiallySortModifiers(SyntaxTokenList modifiers, SyntaxToken modifierToFix)
112+
{
113+
var accessModifiers = modifiers.Where(modifier => GetModifierType(modifier) == ModifierType.Access);
114+
var staticModifiers = modifiers.Where(modifier => GetModifierType(modifier) == ModifierType.Static);
115+
var otherModifiers = modifiers.Where(modifier => GetModifierType(modifier) == ModifierType.Other);
116+
117+
IEnumerable<SyntaxToken> beforeIncluding;
118+
119+
// the modifier to fix is of type other, so we need to sort the whole list of
120+
// modifier list
121+
if (GetModifierType(modifierToFix) == ModifierType.Other)
122+
{
123+
beforeIncluding = accessModifiers
124+
.Concat(staticModifiers)
125+
.Concat(otherModifiers);
126+
}
127+
else if (GetModifierType(modifierToFix) == ModifierType.Static)
128+
{
129+
beforeIncluding = accessModifiers
130+
.Concat(staticModifiers.TakeWhile(modifier => modifier != modifierToFix))
131+
.Concat(new[] { modifierToFix });
132+
}
133+
else
134+
{
135+
beforeIncluding = accessModifiers
136+
.TakeWhile(modifier => modifier != modifierToFix)
137+
.Concat(new[] { modifierToFix });
138+
}
139+
140+
var after = modifiers.Where(modifier => !beforeIncluding.Contains(modifier));
141+
142+
return AdjustTrivia(beforeIncluding.Concat(after), modifiers);
143+
}
144+
145+
/// <summary>
146+
/// Positionally apply the trivia from the old modifier list to the new one.
147+
/// </summary>
148+
/// <param name="newModifiers">The new modifiers.</param>
149+
/// <param name="oldModifiers">The old modifiers.</param>
150+
/// <returns>New modifier list with trivia from the old one.</returns>
151+
private static SyntaxTokenList AdjustTrivia(IEnumerable<SyntaxToken> newModifiers, SyntaxTokenList oldModifiers)
152+
{
153+
var newTokenList = default(SyntaxTokenList);
154+
return newTokenList.AddRange(
155+
newModifiers.Zip(oldModifiers, (m1, m2) => m1.WithTriviaFrom(m2)));
156+
}
157+
158+
private class FixAll : DocumentBasedFixAllProvider
159+
{
160+
public static FixAllProvider Instance { get; } = new FixAll();
161+
162+
protected override string CodeActionTitle => OrderingResources.ModifierOrderCodeFix;
163+
164+
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray<Diagnostic> diagnostics)
165+
{
166+
if (diagnostics.IsEmpty)
167+
{
168+
return null;
169+
}
170+
171+
var syntaxRoot = await document.GetSyntaxRootAsync().ConfigureAwait(false);
172+
173+
// because all modifiers can be fixed in one run, we
174+
// only need to store each declaration once
175+
var trackedDiagnosticMembers = new HashSet<MemberDeclarationSyntax>();
176+
foreach (var diagnostic in diagnostics)
177+
{
178+
var memberDeclaration = syntaxRoot.FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf<MemberDeclarationSyntax>();
179+
if (memberDeclaration == null)
180+
{
181+
continue;
182+
}
183+
184+
var modifierToken = memberDeclaration.FindToken(diagnostic.Location.SourceSpan.Start);
185+
if (GetModifierType(modifierToken) == ModifierType.None)
186+
{
187+
continue;
188+
}
189+
190+
trackedDiagnosticMembers.Add(memberDeclaration);
191+
}
192+
193+
syntaxRoot = syntaxRoot.TrackNodes(trackedDiagnosticMembers);
194+
195+
foreach (var member in trackedDiagnosticMembers)
196+
{
197+
var memberDeclaration = syntaxRoot.GetCurrentNode(member);
198+
var newModifierList = FullySortModifiers(memberDeclaration.GetModifiers());
199+
syntaxRoot = UpdateSyntaxRoot(memberDeclaration, newModifierList, syntaxRoot);
200+
}
201+
202+
return syntaxRoot;
203+
}
204+
}
205+
}
206+
}

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/StyleCop.Analyzers.CodeFixes.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
<Compile Include="NamingRules\SX1309CodeFixProvider.cs" />
105105
<Compile Include="OrderingRules\ElementOrderCodeFixProvider.cs" />
106106
<Compile Include="OrderingRules\SA1205CodeFixProvider.cs" />
107+
<Compile Include="OrderingRules\SA1206CodeFixProvider.cs" />
107108
<Compile Include="OrderingRules\SA1207CodeFixProvider.cs" />
108109
<Compile Include="OrderingRules\SA1212SA1213CodeFixProvider.cs" />
109110
<Compile Include="OrderingRules\UsingCodeFixProvider.cs" />

0 commit comments

Comments
 (0)