Skip to content

Commit 6d80369

Browse files
authored
Merge pull request #2050 from vweijsters/implement-1787
Implement 1787
2 parents f50ce2e + 6003510 commit 6d80369

35 files changed

Lines changed: 2393 additions & 728 deletions

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1516CodeFixProvider.cs

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,44 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
3939

4040
foreach (Diagnostic diagnostic in context.Diagnostics)
4141
{
42+
var insertBlankLine = DetermineCodeFixAction(diagnostic);
43+
if (insertBlankLine == null)
44+
{
45+
continue;
46+
}
47+
4248
context.RegisterCodeFix(
4349
CodeAction.Create(
44-
LayoutResources.SA1516CodeFix,
45-
cancellationToken => GetTransformedDocumentAsync(context.Document, syntaxRoot, diagnostic, context.CancellationToken),
50+
insertBlankLine.Value ? LayoutResources.SA1516CodeFixInsert : LayoutResources.SA1516CodeFixRemove,
51+
cancellationToken => GetTransformedDocumentAsync(context.Document, syntaxRoot, diagnostic, insertBlankLine.Value, context.CancellationToken),
4652
nameof(SA1516CodeFixProvider)),
4753
diagnostic);
4854
}
4955
}
5056

51-
private static Task<Document> GetTransformedDocumentAsync(Document document, SyntaxNode syntaxRoot, Diagnostic diagnostic, CancellationToken cancellationToken)
57+
private static bool? DetermineCodeFixAction(Diagnostic diagnostic)
58+
{
59+
string codeFixAction;
60+
61+
if (!diagnostic.Properties.TryGetValue(SA1516ElementsMustBeSeparatedByBlankLine.CodeFixActionKey, out codeFixAction))
62+
{
63+
return null;
64+
}
65+
66+
switch (codeFixAction)
67+
{
68+
case SA1516ElementsMustBeSeparatedByBlankLine.InsertBlankLineValue:
69+
return true;
70+
71+
case SA1516ElementsMustBeSeparatedByBlankLine.RemoveBlankLinesValue:
72+
return false;
73+
74+
default:
75+
return null;
76+
}
77+
}
78+
79+
private static Task<Document> GetTransformedDocumentAsync(Document document, SyntaxNode syntaxRoot, Diagnostic diagnostic, bool insertBlankLine, CancellationToken cancellationToken)
5280
{
5381
var node = syntaxRoot.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
5482
node = GetRelevantNode(node);
@@ -58,18 +86,32 @@ private static Task<Document> GetTransformedDocumentAsync(Document document, Syn
5886
return Task.FromResult(document);
5987
}
6088

61-
var leadingTrivia = node.GetLeadingTrivia();
62-
63-
var newTriviaList = leadingTrivia;
64-
newTriviaList = newTriviaList.Insert(0, SyntaxFactory.CarriageReturnLineFeed);
65-
66-
var newNode = node.WithLeadingTrivia(newTriviaList);
67-
var newSyntaxRoot = syntaxRoot.ReplaceNode(node, newNode);
89+
// Using the token replacement here to use the same strategy as the FixAll.
90+
var firstToken = node.GetFirstToken();
91+
var newToken = ProcessToken(firstToken, insertBlankLine);
92+
var newSyntaxRoot = syntaxRoot.ReplaceToken(firstToken, newToken);
6893
var newDocument = document.WithSyntaxRoot(newSyntaxRoot);
6994

7095
return Task.FromResult(newDocument);
7196
}
7297

98+
private static SyntaxToken ProcessToken(SyntaxToken token, bool insertBlankLine)
99+
{
100+
var leadingTrivia = token.LeadingTrivia;
101+
SyntaxTriviaList newLeadingTrivia;
102+
103+
if (insertBlankLine)
104+
{
105+
newLeadingTrivia = leadingTrivia.Insert(0, SyntaxFactory.CarriageReturnLineFeed);
106+
}
107+
else
108+
{
109+
newLeadingTrivia = leadingTrivia.WithoutBlankLines();
110+
}
111+
112+
return token.WithLeadingTrivia(newLeadingTrivia);
113+
}
114+
73115
private static SyntaxNode GetRelevantNode(SyntaxNode innerNode)
74116
{
75117
SyntaxNode currentNode = innerNode;
@@ -112,7 +154,7 @@ private class FixAll : DocumentBasedFixAllProvider
112154
new FixAll();
113155

114156
protected override string CodeActionTitle =>
115-
LayoutResources.SA1516CodeFix;
157+
LayoutResources.SA1516CodeFixAll;
116158

117159
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray<Diagnostic> diagnostics)
118160
{
@@ -123,26 +165,29 @@ protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fi
123165

124166
var syntaxRoot = await document.GetSyntaxRootAsync().ConfigureAwait(false);
125167

126-
List<SyntaxNode> nodes = new List<SyntaxNode>();
168+
// Using token replacement, because node replacement will do nothing when replacing child nodes from a replaced parent node.
169+
Dictionary<SyntaxToken, SyntaxToken> replaceMap = new Dictionary<SyntaxToken, SyntaxToken>();
127170

128171
foreach (var diagnostic in diagnostics)
129172
{
173+
var insertBlankLine = DetermineCodeFixAction(diagnostic);
174+
if (insertBlankLine == null)
175+
{
176+
continue;
177+
}
178+
130179
var node = syntaxRoot.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
131180
node = GetRelevantNode(node);
132181

133182
if (node != null)
134183
{
135-
nodes.Add(node);
184+
var firstToken = node.GetFirstToken();
185+
186+
replaceMap[firstToken] = ProcessToken(firstToken, insertBlankLine.Value);
136187
}
137188
}
138189

139-
return syntaxRoot.ReplaceNodes(nodes, (oldNode, newNode) =>
140-
{
141-
var newTriviaList = newNode.GetLeadingTrivia();
142-
newTriviaList = newTriviaList.Insert(0, SyntaxFactory.CarriageReturnLineFeed);
143-
144-
return newNode.WithLeadingTrivia(newTriviaList);
145-
});
190+
return syntaxRoot.ReplaceTokens(replaceMap.Keys, (original, rewritten) => replaceMap[original]);
146191
}
147192
}
148193
}

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1518CodeFixProvider.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,13 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
5353
/// </summary>
5454
/// <param name="document">The document to be changed.</param>
5555
/// <param name="diagnostic">The diagnostic to fix.</param>
56-
/// <param name="newlineAtEndOfFile">A <see cref="EndOfFileHandling"/> value indicating the desired behavior.</param>
56+
/// <param name="newlineAtEndOfFile">A <see cref="OptionSetting"/> value indicating the desired behavior.</param>
5757
/// <param name="cancellationToken">The cancellation token associated with the fix action.</param>
5858
/// <returns>The transformed document.</returns>
59-
private static async Task<Document> FixEndOfFileAsync(Document document, Diagnostic diagnostic, EndOfFileHandling newlineAtEndOfFile, CancellationToken cancellationToken)
59+
private static async Task<Document> FixEndOfFileAsync(Document document, Diagnostic diagnostic, OptionSetting newlineAtEndOfFile, CancellationToken cancellationToken)
6060
{
6161
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
62-
string replacement = newlineAtEndOfFile == EndOfFileHandling.Omit ? string.Empty : "\r\n";
62+
string replacement = newlineAtEndOfFile == OptionSetting.Omit ? string.Empty : "\r\n";
6363
return document.WithText(text.WithChanges(new TextChange(diagnostic.Location.SourceSpan, replacement)));
6464
}
6565

0 commit comments

Comments
 (0)