Skip to content

Commit 5463da7

Browse files
committed
Merge pull request #1577 from vweijsters/fix-1556
2 parents 6b5f18b + 3196588 commit 5463da7

2 files changed

Lines changed: 103 additions & 21 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1106UnitTests.cs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,83 @@ public async Task TestMemberAsync(string declaration)
340340
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
341341
}
342342

343+
/// <summary>
344+
/// Verifies that the code fix will remove all unnecessary whitespace.
345+
/// This is a regression for #1556
346+
/// </summary>
347+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
348+
[Fact]
349+
public async Task VerifyCodeFixWillRemoveUnnecessaryWhitespaceAsync()
350+
{
351+
var testCode = @"
352+
class TestClass
353+
{
354+
public void TestMethod1()
355+
{
356+
throw new System.NotImplementedException(); ;
357+
}
358+
359+
public void TestMethod2()
360+
{
361+
throw new System.NotImplementedException(); /* c1 */ ;
362+
}
363+
}";
364+
var fixedCode = @"
365+
class TestClass
366+
{
367+
public void TestMethod1()
368+
{
369+
throw new System.NotImplementedException();
370+
}
371+
372+
public void TestMethod2()
373+
{
374+
throw new System.NotImplementedException(); /* c1 */
375+
}
376+
}";
377+
378+
DiagnosticResult[] expected =
379+
{
380+
this.CSharpDiagnostic().WithLocation(6, 53),
381+
this.CSharpDiagnostic().WithLocation(11, 62)
382+
};
383+
384+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
385+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
386+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
387+
}
388+
389+
/// <summary>
390+
/// Verifies that the code fix will not remove relevant trivia.
391+
/// </summary>
392+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
393+
[Fact]
394+
public async Task VerifyCodeFixWillNotRemoveTriviaAsync()
395+
{
396+
var testCode = @"
397+
class TestClass
398+
{
399+
public void TestMethod()
400+
{
401+
/* do nothing */ ;
402+
}
403+
}";
404+
var fixedCode = @"
405+
class TestClass
406+
{
407+
public void TestMethod()
408+
{
409+
/* do nothing */
410+
}
411+
}";
412+
413+
var expected = this.CSharpDiagnostic().WithLocation(6, 26);
414+
415+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
416+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
417+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
418+
}
419+
343420
/// <inheritdoc/>
344421
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
345422
{

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1106CodeFixProvider.cs

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33

44
namespace StyleCop.Analyzers.ReadabilityRules
55
{
6-
using System.Collections.Generic;
76
using System.Collections.Immutable;
87
using System.Composition;
9-
using System.Linq;
108
using System.Threading;
119
using System.Threading.Tasks;
1210
using Microsoft.CodeAnalysis;
@@ -16,7 +14,6 @@ namespace StyleCop.Analyzers.ReadabilityRules
1614
using Microsoft.CodeAnalysis.CSharp.Syntax;
1715
using Microsoft.CodeAnalysis.Text;
1816
using StyleCop.Analyzers.Helpers;
19-
using StyleCop.Analyzers.SpacingRules;
2017

2118
/// <summary>
2219
/// This class provides a code fix for <see cref="SA1106CodeMustNotContainEmptyStatements"/>.
@@ -25,11 +22,9 @@ namespace StyleCop.Analyzers.ReadabilityRules
2522
[Shared]
2623
internal class SA1106CodeFixProvider : CodeFixProvider
2724
{
28-
private static readonly ImmutableArray<string> FixableDiagnostics =
29-
ImmutableArray.Create(SA1106CodeMustNotContainEmptyStatements.DiagnosticId);
30-
3125
/// <inheritdoc/>
32-
public override ImmutableArray<string> FixableDiagnosticIds => FixableDiagnostics;
26+
public override ImmutableArray<string> FixableDiagnosticIds { get; }
27+
= ImmutableArray.Create(SA1106CodeMustNotContainEmptyStatements.DiagnosticId);
3328

3429
/// <inheritdoc/>
3530
public override FixAllProvider GetFixAllProvider()
@@ -42,11 +37,6 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
4237
{
4338
foreach (var diagnostic in context.Diagnostics)
4439
{
45-
if (!diagnostic.Id.Equals(SA1106CodeMustNotContainEmptyStatements.DiagnosticId))
46-
{
47-
continue;
48-
}
49-
5040
context.RegisterCodeFix(
5141
CodeAction.Create(
5242
ReadabilityResources.SA1106CodeFix,
@@ -62,10 +52,6 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen
6252
{
6353
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
6454
var token = root.FindToken(diagnostic.Location.SourceSpan.Start);
65-
if (token.IsMissingOrDefault())
66-
{
67-
return document;
68-
}
6955

7056
if (!token.Parent.IsKind(SyntaxKind.EmptyStatement))
7157
{
@@ -106,20 +92,39 @@ private static async Task<Document> RemoveEmptyStatementAsync(Document document,
10692

10793
private static async Task<Document> RemoveSemicolonTextAsync(Document document, SyntaxToken token, CancellationToken cancellationToken)
10894
{
95+
TextChange textChange;
96+
10997
SourceText sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
11098
TextLine line = sourceText.Lines.GetLineFromPosition(token.SpanStart);
11199
if (sourceText.ToString(line.Span).Trim() == token.Text)
112100
{
113101
// remove the line containing the semicolon token
114-
TextChange textChange = new TextChange(line.SpanIncludingLineBreak, string.Empty);
102+
textChange = new TextChange(line.SpanIncludingLineBreak, string.Empty);
115103
return document.WithText(sourceText.WithChanges(textChange));
116104
}
105+
106+
TextSpan spanToRemove;
107+
var whitespaceIndex = TriviaHelper.IndexOfTrailingWhitespace(token.LeadingTrivia);
108+
if (whitespaceIndex >= 0)
109+
{
110+
spanToRemove = TextSpan.FromBounds(token.LeadingTrivia[whitespaceIndex].Span.Start, token.Span.End);
111+
}
117112
else
118113
{
119-
// remove just the semicolon
120-
TextChange textChange = new TextChange(token.Span, string.Empty);
121-
return document.WithText(sourceText.WithChanges(textChange));
114+
var previousToken = token.GetPreviousToken();
115+
whitespaceIndex = TriviaHelper.IndexOfTrailingWhitespace(previousToken.TrailingTrivia);
116+
if (whitespaceIndex >= 0)
117+
{
118+
spanToRemove = TextSpan.FromBounds(previousToken.TrailingTrivia[whitespaceIndex].Span.Start, token.Span.End);
119+
}
120+
else
121+
{
122+
spanToRemove = token.Span;
123+
}
122124
}
125+
126+
textChange = new TextChange(spanToRemove, string.Empty);
127+
return document.WithText(sourceText.WithChanges(textChange));
123128
}
124129
}
125-
}
130+
}

0 commit comments

Comments
 (0)