Skip to content

Commit 13923ad

Browse files
committed
Improved SA1500 FixAll behavior
1 parent 7f711ee commit 13923ad

1 file changed

Lines changed: 103 additions & 81 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1500CodeFixProvider.cs

Lines changed: 103 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -25,28 +25,20 @@ namespace StyleCop.Analyzers.LayoutRules
2525
[Shared]
2626
internal class SA1500CodeFixProvider : CodeFixProvider
2727
{
28-
private static readonly ImmutableArray<string> FixableDiagnostics =
29-
ImmutableArray.Create(SA1500CurlyBracketsForMultiLineStatementsMustNotShareLine.DiagnosticId);
30-
3128
/// <inheritdoc/>
32-
public override ImmutableArray<string> FixableDiagnosticIds
33-
{
34-
get
35-
{
36-
return FixableDiagnostics;
37-
}
38-
}
29+
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
30+
ImmutableArray.Create(SA1500CurlyBracketsForMultiLineStatementsMustNotShareLine.DiagnosticId);
3931

4032
/// <inheritdoc/>
4133
public override FixAllProvider GetFixAllProvider()
4234
{
43-
return CustomFixAllProviders.BatchFixer;
35+
return FixAll.Instance;
4436
}
4537

4638
/// <inheritdoc/>
4739
public override Task RegisterCodeFixesAsync(CodeFixContext context)
4840
{
49-
foreach (Diagnostic diagnostic in context.Diagnostics.Where(d => FixableDiagnostics.Contains(d.Id)))
41+
foreach (Diagnostic diagnostic in context.Diagnostics)
5042
{
5143
context.RegisterCodeFix(CodeAction.Create(LayoutResources.SA1500CodeFix, token => GetTransformedDocumentAsync(context.Document, diagnostic, token), equivalenceKey: nameof(SA1500CodeFixProvider)), diagnostic);
5244
}
@@ -58,79 +50,98 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen
5850
{
5951
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
6052

61-
var curlyBracketToken = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start);
62-
var curlyBracketLine = curlyBracketToken.GetLineSpan().StartLinePosition.Line;
63-
var curlyBracketReplacementToken = curlyBracketToken;
53+
var braceToken = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start);
54+
var tokenReplacements = GenerateBraceFixes(document, ImmutableArray.Create(braceToken));
6455

65-
var indentationOptions = IndentationOptions.FromDocument(document);
66-
var indentationSteps = DetermineIndentationSteps(indentationOptions, curlyBracketToken);
67-
68-
var previousToken = curlyBracketToken.GetPreviousToken();
69-
var nextToken = curlyBracketToken.GetNextToken();
56+
var newSyntaxRoot = syntaxRoot.ReplaceTokens(tokenReplacements.Keys, (originalToken, rewrittenToken) => tokenReplacements[originalToken]);
57+
return document.WithSyntaxRoot(newSyntaxRoot);
58+
}
7059

71-
var rewriter = new Rewriter();
60+
private static Dictionary<SyntaxToken, SyntaxToken> GenerateBraceFixes(Document document, ImmutableArray<SyntaxToken> braceTokens)
61+
{
62+
var tokenReplacements = new Dictionary<SyntaxToken, SyntaxToken>();
7263

73-
if (IsAccessorWithSingleLineBlock(previousToken, curlyBracketToken))
64+
foreach (var braceToken in braceTokens)
7465
{
75-
var newTrailingTrivia = previousToken.TrailingTrivia
76-
.WithoutTrailingWhitespace()
77-
.Add(SyntaxFactory.Space);
66+
var braceLine = LocationHelpers.GetLineSpan(braceToken).StartLinePosition.Line;
67+
var braceReplacementToken = braceToken;
7868

79-
rewriter.AddReplacement(previousToken, previousToken.WithTrailingTrivia(newTrailingTrivia));
69+
var indentationOptions = IndentationOptions.FromDocument(document);
70+
var indentationSteps = DetermineIndentationSteps(indentationOptions, braceToken);
8071

81-
curlyBracketReplacementToken = curlyBracketReplacementToken.WithLeadingTrivia(curlyBracketToken.LeadingTrivia.WithoutLeadingWhitespace());
82-
}
83-
else
84-
{
85-
// Check if we need to apply a fix before the curly bracket
86-
if (previousToken.GetLineSpan().StartLinePosition.Line == curlyBracketLine)
72+
var previousToken = braceToken.GetPreviousToken();
73+
var nextToken = braceToken.GetNextToken();
74+
75+
if (IsAccessorWithSingleLineBlock(previousToken, braceToken))
8776
{
88-
var sharedTrivia = curlyBracketReplacementToken.LeadingTrivia.WithoutTrailingWhitespace();
89-
var previousTokenNewTrailingTrivia = previousToken.TrailingTrivia
90-
.WithoutTrailingWhitespace()
91-
.AddRange(sharedTrivia)
92-
.Add(SyntaxFactory.CarriageReturnLineFeed);
77+
if (!braceTokens.Contains(previousToken))
78+
{
79+
var newTrailingTrivia = previousToken.TrailingTrivia
80+
.WithoutTrailingWhitespace()
81+
.Add(SyntaxFactory.Space);
9382

94-
rewriter.AddReplacement(previousToken, previousToken.WithTrailingTrivia(previousTokenNewTrailingTrivia));
83+
AddReplacement(tokenReplacements, previousToken, previousToken.WithTrailingTrivia(newTrailingTrivia));
84+
}
9585

96-
curlyBracketReplacementToken = curlyBracketReplacementToken.WithLeadingTrivia(IndentationHelper.GenerateWhitespaceTrivia(indentationOptions, indentationSteps));
86+
braceReplacementToken = braceReplacementToken.WithLeadingTrivia(braceToken.LeadingTrivia.WithoutLeadingWhitespace());
9787
}
98-
99-
// Check if we need to apply a fix after the curly bracket
100-
// if a closing curly bracket is followed by a semi-colon or closing paren, no fix is needed.
101-
if ((nextToken.GetLineSpan().StartLinePosition.Line == curlyBracketLine) &&
102-
(!curlyBracketToken.IsKind(SyntaxKind.CloseBraceToken) || !IsValidFollowingToken(nextToken)))
88+
else
10389
{
104-
var sharedTrivia = nextToken.LeadingTrivia.WithoutTrailingWhitespace();
105-
var newTrailingTrivia = curlyBracketReplacementToken.TrailingTrivia
106-
.WithoutTrailingWhitespace()
107-
.AddRange(sharedTrivia)
108-
.Add(SyntaxFactory.CarriageReturnLineFeed);
109-
110-
int newIndentationSteps;
111-
if (curlyBracketToken.IsKind(SyntaxKind.OpenBraceToken))
90+
// Check if we need to apply a fix before the curly bracket
91+
if (LocationHelpers.GetLineSpan(previousToken).StartLinePosition.Line == braceLine)
11292
{
113-
newIndentationSteps = indentationSteps + 1;
93+
if (!braceTokens.Contains(previousToken))
94+
{
95+
var sharedTrivia = braceReplacementToken.LeadingTrivia.WithoutTrailingWhitespace();
96+
var previousTokenNewTrailingTrivia = previousToken.TrailingTrivia
97+
.WithoutTrailingWhitespace()
98+
.AddRange(sharedTrivia)
99+
.Add(SyntaxFactory.CarriageReturnLineFeed);
100+
101+
AddReplacement(tokenReplacements, previousToken, previousToken.WithTrailingTrivia(previousTokenNewTrailingTrivia));
102+
}
103+
104+
braceReplacementToken = braceReplacementToken.WithLeadingTrivia(IndentationHelper.GenerateWhitespaceTrivia(indentationOptions, indentationSteps));
114105
}
115-
else if (nextToken.IsKind(SyntaxKind.CloseBraceToken))
116-
{
117-
newIndentationSteps = Math.Max(0, indentationSteps - 1);
118-
}
119-
else
106+
107+
// Check if we need to apply a fix after the curly bracket
108+
// if a closing curly bracket is followed by a semi-colon or closing paren, no fix is needed.
109+
if ((LocationHelpers.GetLineSpan(nextToken).StartLinePosition.Line == braceLine) &&
110+
(!braceToken.IsKind(SyntaxKind.CloseBraceToken) || !IsValidFollowingToken(nextToken)))
120111
{
121-
newIndentationSteps = indentationSteps;
112+
var sharedTrivia = nextToken.LeadingTrivia.WithoutTrailingWhitespace();
113+
var newTrailingTrivia = braceReplacementToken.TrailingTrivia
114+
.WithoutTrailingWhitespace()
115+
.AddRange(sharedTrivia)
116+
.Add(SyntaxFactory.CarriageReturnLineFeed);
117+
118+
if (!braceTokens.Contains(nextToken))
119+
{
120+
int newIndentationSteps;
121+
if (braceToken.IsKind(SyntaxKind.OpenBraceToken))
122+
{
123+
newIndentationSteps = indentationSteps + 1;
124+
}
125+
else if (nextToken.IsKind(SyntaxKind.CloseBraceToken))
126+
{
127+
newIndentationSteps = Math.Max(0, indentationSteps - 1);
128+
}
129+
else
130+
{
131+
newIndentationSteps = indentationSteps;
132+
}
133+
134+
AddReplacement(tokenReplacements, nextToken, nextToken.WithLeadingTrivia(IndentationHelper.GenerateWhitespaceTrivia(indentationOptions, newIndentationSteps)));
135+
}
136+
137+
braceReplacementToken = braceReplacementToken.WithTrailingTrivia(newTrailingTrivia);
122138
}
123-
124-
rewriter.AddReplacement(nextToken, nextToken.WithLeadingTrivia(IndentationHelper.GenerateWhitespaceTrivia(indentationOptions, newIndentationSteps)));
125-
126-
curlyBracketReplacementToken = curlyBracketReplacementToken.WithTrailingTrivia(newTrailingTrivia);
127139
}
128-
}
129140

130-
rewriter.AddReplacement(curlyBracketToken, curlyBracketReplacementToken);
141+
AddReplacement(tokenReplacements, braceToken, braceReplacementToken);
142+
}
131143

132-
var newSyntaxRoot = rewriter.Visit(syntaxRoot).WithoutFormatting();
133-
return document.WithSyntaxRoot(newSyntaxRoot);
144+
return tokenReplacements;
134145
}
135146

136147
private static bool IsAccessorWithSingleLineBlock(SyntaxToken previousToken, SyntaxToken curlyBracketToken)
@@ -170,7 +181,7 @@ private static bool IsAccessorWithSingleLineBlock(SyntaxToken previousToken, Syn
170181
}
171182
}
172183

173-
return curlyBracketToken.GetLineSpan().StartLinePosition.Line == token.GetLineSpan().StartLinePosition.Line;
184+
return LocationHelpers.GetLineSpan(curlyBracketToken).StartLinePosition.Line == LocationHelpers.GetLineSpan(token).StartLinePosition.Line;
174185
}
175186

176187
private static bool IsValidFollowingToken(SyntaxToken nextToken)
@@ -229,28 +240,39 @@ private static bool ContainsStartOfLine(SyntaxToken token, int startLine)
229240

230241
private static LinePosition GetTokenStartLinePosition(SyntaxToken token)
231242
{
232-
return Location.Create(token.SyntaxTree, token.FullSpan).GetLineSpan().StartLinePosition;
243+
return token.SyntaxTree.GetLineSpan(token.FullSpan).StartLinePosition;
233244
}
234245

235-
private class Rewriter : CSharpSyntaxRewriter
246+
private static void AddReplacement(Dictionary<SyntaxToken, SyntaxToken> tokenReplacements, SyntaxToken originalToken, SyntaxToken replacementToken)
236247
{
237-
private readonly Dictionary<SyntaxToken, SyntaxToken> tokenReplacements = new Dictionary<SyntaxToken, SyntaxToken>();
248+
tokenReplacements[originalToken] = replacementToken;
249+
}
238250

239-
public void AddReplacement(SyntaxToken originalToken, SyntaxToken replacementToken)
240-
{
241-
this.tokenReplacements.Add(originalToken, replacementToken);
242-
}
251+
private class FixAll : DocumentBasedFixAllProvider
252+
{
253+
public static FixAllProvider Instance { get; } =
254+
new FixAll();
243255

244-
public override SyntaxToken VisitToken(SyntaxToken token)
245-
{
246-
SyntaxToken replacementToken;
256+
protected override string CodeActionTitle =>
257+
LayoutResources.SA1503CodeFix;
247258

248-
if (this.tokenReplacements.TryGetValue(token, out replacementToken))
259+
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document)
260+
{
261+
var diagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false);
262+
if (diagnostics.IsEmpty)
249263
{
250-
return replacementToken;
264+
return null;
251265
}
252266

253-
return base.VisitToken(token);
267+
SyntaxNode syntaxRoot = await document.GetSyntaxRootAsync().ConfigureAwait(false);
268+
269+
var tokens = diagnostics
270+
.Select(diagnostic => syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start))
271+
.OrderBy(token => token.SpanStart);
272+
273+
var tokenReplacements = GenerateBraceFixes(document, tokens.ToImmutableArray());
274+
275+
return syntaxRoot.ReplaceTokens(tokenReplacements.Keys, (originalToken, rewrittenToken) => tokenReplacements[originalToken]);
254276
}
255277
}
256278
}

0 commit comments

Comments
 (0)