Skip to content

Commit 51cab26

Browse files
committed
Merge pull request #1576 from vweijsters/fix-1565
2 parents 5463da7 + 5d9fdb9 commit 51cab26

2 files changed

Lines changed: 146 additions & 80 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1500/SA1500UnitTests.Properties.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,5 +415,51 @@ public bool Property11
415415
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
416416
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
417417
}
418+
419+
/// <summary>
420+
/// Verifies that a single line accessor with an embedded block will be handled correctly.
421+
/// </summary>
422+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
423+
[Fact]
424+
public async Task TestSingleLineAccessorWithEmbeddedBlockAsync()
425+
{
426+
var testCode = @"
427+
public class TestClass
428+
{
429+
public int[] TestProperty
430+
{
431+
get {
432+
{
433+
return new[] { 1, 2, 3 }; } }
434+
}
435+
}
436+
";
437+
438+
var fixedTestCode = @"
439+
public class TestClass
440+
{
441+
public int[] TestProperty
442+
{
443+
get
444+
{
445+
{
446+
return new[] { 1, 2, 3 };
447+
}
448+
}
449+
}
450+
}
451+
";
452+
453+
DiagnosticResult[] expected =
454+
{
455+
this.CSharpDiagnostic().WithLocation(6, 13),
456+
this.CSharpDiagnostic().WithLocation(8, 43),
457+
this.CSharpDiagnostic().WithLocation(8, 45)
458+
};
459+
460+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
461+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
462+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
463+
}
418464
}
419465
}

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

Lines changed: 100 additions & 80 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,95 @@ 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
77+
var newTrailingTrivia = previousToken.TrailingTrivia
9078
.WithoutTrailingWhitespace()
91-
.AddRange(sharedTrivia)
92-
.Add(SyntaxFactory.CarriageReturnLineFeed);
79+
.Add(SyntaxFactory.Space);
9380

94-
rewriter.AddReplacement(previousToken, previousToken.WithTrailingTrivia(previousTokenNewTrailingTrivia));
81+
AddReplacement(tokenReplacements, previousToken, previousToken.WithTrailingTrivia(newTrailingTrivia));
9582

96-
curlyBracketReplacementToken = curlyBracketReplacementToken.WithLeadingTrivia(IndentationHelper.GenerateWhitespaceTrivia(indentationOptions, indentationSteps));
83+
braceReplacementToken = braceReplacementToken.WithLeadingTrivia(braceToken.LeadingTrivia.WithoutLeadingWhitespace());
9784
}
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)))
85+
else
10386
{
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))
87+
// Check if we need to apply a fix before the curly bracket
88+
if (LocationHelpers.GetLineSpan(previousToken).StartLinePosition.Line == braceLine)
11289
{
113-
newIndentationSteps = indentationSteps + 1;
90+
if (!braceTokens.Contains(previousToken))
91+
{
92+
var sharedTrivia = braceReplacementToken.LeadingTrivia.WithoutTrailingWhitespace();
93+
var previousTokenNewTrailingTrivia = previousToken.TrailingTrivia
94+
.WithoutTrailingWhitespace()
95+
.AddRange(sharedTrivia)
96+
.Add(SyntaxFactory.CarriageReturnLineFeed);
97+
98+
AddReplacement(tokenReplacements, previousToken, previousToken.WithTrailingTrivia(previousTokenNewTrailingTrivia));
99+
}
100+
101+
braceReplacementToken = braceReplacementToken.WithLeadingTrivia(IndentationHelper.GenerateWhitespaceTrivia(indentationOptions, indentationSteps));
114102
}
115-
else if (nextToken.IsKind(SyntaxKind.CloseBraceToken))
116-
{
117-
newIndentationSteps = Math.Max(0, indentationSteps - 1);
118-
}
119-
else
103+
104+
// Check if we need to apply a fix after the curly bracket
105+
// if a closing curly bracket is followed by a semi-colon or closing paren, no fix is needed.
106+
if ((LocationHelpers.GetLineSpan(nextToken).StartLinePosition.Line == braceLine) &&
107+
(!braceToken.IsKind(SyntaxKind.CloseBraceToken) || !IsValidFollowingToken(nextToken)))
120108
{
121-
newIndentationSteps = indentationSteps;
109+
var sharedTrivia = nextToken.LeadingTrivia.WithoutTrailingWhitespace();
110+
var newTrailingTrivia = braceReplacementToken.TrailingTrivia
111+
.WithoutTrailingWhitespace()
112+
.AddRange(sharedTrivia)
113+
.Add(SyntaxFactory.CarriageReturnLineFeed);
114+
115+
if (!braceTokens.Contains(nextToken))
116+
{
117+
int newIndentationSteps;
118+
if (braceToken.IsKind(SyntaxKind.OpenBraceToken))
119+
{
120+
newIndentationSteps = indentationSteps + 1;
121+
}
122+
else if (nextToken.IsKind(SyntaxKind.CloseBraceToken))
123+
{
124+
newIndentationSteps = Math.Max(0, indentationSteps - 1);
125+
}
126+
else
127+
{
128+
newIndentationSteps = indentationSteps;
129+
}
130+
131+
AddReplacement(tokenReplacements, nextToken, nextToken.WithLeadingTrivia(IndentationHelper.GenerateWhitespaceTrivia(indentationOptions, newIndentationSteps)));
132+
}
133+
134+
braceReplacementToken = braceReplacementToken.WithTrailingTrivia(newTrailingTrivia);
122135
}
123-
124-
rewriter.AddReplacement(nextToken, nextToken.WithLeadingTrivia(IndentationHelper.GenerateWhitespaceTrivia(indentationOptions, newIndentationSteps)));
125-
126-
curlyBracketReplacementToken = curlyBracketReplacementToken.WithTrailingTrivia(newTrailingTrivia);
127136
}
128-
}
129137

130-
rewriter.AddReplacement(curlyBracketToken, curlyBracketReplacementToken);
138+
AddReplacement(tokenReplacements, braceToken, braceReplacementToken);
139+
}
131140

132-
var newSyntaxRoot = rewriter.Visit(syntaxRoot).WithoutFormatting();
133-
return document.WithSyntaxRoot(newSyntaxRoot);
141+
return tokenReplacements;
134142
}
135143

136144
private static bool IsAccessorWithSingleLineBlock(SyntaxToken previousToken, SyntaxToken curlyBracketToken)
@@ -170,7 +178,7 @@ private static bool IsAccessorWithSingleLineBlock(SyntaxToken previousToken, Syn
170178
}
171179
}
172180

173-
return curlyBracketToken.GetLineSpan().StartLinePosition.Line == token.GetLineSpan().StartLinePosition.Line;
181+
return LocationHelpers.GetLineSpan(curlyBracketToken).StartLinePosition.Line == LocationHelpers.GetLineSpan(token).StartLinePosition.Line;
174182
}
175183

176184
private static bool IsValidFollowingToken(SyntaxToken nextToken)
@@ -229,28 +237,40 @@ private static bool ContainsStartOfLine(SyntaxToken token, int startLine)
229237

230238
private static LinePosition GetTokenStartLinePosition(SyntaxToken token)
231239
{
232-
return Location.Create(token.SyntaxTree, token.FullSpan).GetLineSpan().StartLinePosition;
240+
return token.SyntaxTree.GetLineSpan(token.FullSpan).StartLinePosition;
233241
}
234242

235-
private class Rewriter : CSharpSyntaxRewriter
243+
private static void AddReplacement(Dictionary<SyntaxToken, SyntaxToken> tokenReplacements, SyntaxToken originalToken, SyntaxToken replacementToken)
236244
{
237-
private readonly Dictionary<SyntaxToken, SyntaxToken> tokenReplacements = new Dictionary<SyntaxToken, SyntaxToken>();
245+
tokenReplacements[originalToken] = replacementToken;
246+
}
238247

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

244-
public override SyntaxToken VisitToken(SyntaxToken token)
245-
{
246-
SyntaxToken replacementToken;
253+
protected override string CodeActionTitle =>
254+
LayoutResources.SA1500CodeFix;
247255

248-
if (this.tokenReplacements.TryGetValue(token, out replacementToken))
256+
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document)
257+
{
258+
var diagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false);
259+
if (diagnostics.IsEmpty)
249260
{
250-
return replacementToken;
261+
return null;
251262
}
252263

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

0 commit comments

Comments
 (0)