Skip to content

Commit c9fb5f0

Browse files
committed
Merge pull request #1466 from sharwell/sa1027-fixall
SA1027 fix all provider and bug fixes
2 parents 6875128 + 105f1d0 commit c9fb5f0

3 files changed

Lines changed: 186 additions & 27 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test/SpacingRules/SA1027UnitTests.cs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,131 @@ public void Bar()
9797
await this.VerifyCSharpFixAsync(testCode, fixedTestCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
9898
}
9999

100+
[Fact]
101+
public async Task TestInvalidTabsInDocumentationCommentsAsync()
102+
{
103+
var testCode =
104+
"\t/// <summary>\r\n" +
105+
"\t/// foo bar\r\n" +
106+
"\t/// </summary>\r\n" +
107+
"\tpublic class Foo\r\n" +
108+
"\t{\r\n" +
109+
"\t \t/// <MyElement> Value </MyElement>\r\n" +
110+
"\t\t/// <MyElement> Value </MyElement>\r\n" +
111+
"\t}\r\n";
112+
113+
var fixedTestCode = @" /// <summary>
114+
/// foo bar
115+
/// </summary>
116+
public class Foo
117+
{
118+
/// <MyElement> Value </MyElement>
119+
/// <MyElement> Value </MyElement>
120+
}
121+
";
122+
123+
DiagnosticResult[] expected =
124+
{
125+
this.CSharpDiagnostic().WithLocation(1, 1),
126+
this.CSharpDiagnostic().WithLocation(2, 1),
127+
this.CSharpDiagnostic().WithLocation(3, 1),
128+
this.CSharpDiagnostic().WithLocation(4, 1),
129+
this.CSharpDiagnostic().WithLocation(5, 1),
130+
this.CSharpDiagnostic().WithLocation(6, 1),
131+
this.CSharpDiagnostic().WithLocation(7, 1),
132+
this.CSharpDiagnostic().WithLocation(8, 1),
133+
};
134+
135+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
136+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
137+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
138+
}
139+
140+
[Fact]
141+
public async Task TestInvalidTabsInCommentsAsync()
142+
{
143+
var testCode =
144+
"\tpublic class Foo\r\n" +
145+
"\t{\r\n" +
146+
"\t\tpublic void Bar()\r\n" +
147+
"\t\t{\r\n" +
148+
"\t\t \t//\tComment\t\t1\r\n" +
149+
"\t \t\t// Comment 2\r\n" +
150+
"\t\t}\r\n" +
151+
"\t}\r\n";
152+
153+
var fixedTestCode = @" public class Foo
154+
{
155+
public void Bar()
156+
{
157+
// Comment 1
158+
// Comment 2
159+
}
160+
}
161+
";
162+
163+
DiagnosticResult[] expected =
164+
{
165+
this.CSharpDiagnostic().WithLocation(1, 1),
166+
this.CSharpDiagnostic().WithLocation(2, 1),
167+
this.CSharpDiagnostic().WithLocation(3, 1),
168+
this.CSharpDiagnostic().WithLocation(4, 1),
169+
this.CSharpDiagnostic().WithLocation(5, 1),
170+
this.CSharpDiagnostic().WithLocation(5, 5),
171+
this.CSharpDiagnostic().WithLocation(6, 1),
172+
this.CSharpDiagnostic().WithLocation(7, 1),
173+
this.CSharpDiagnostic().WithLocation(8, 1),
174+
};
175+
176+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
177+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
178+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
179+
}
180+
181+
[Fact]
182+
public async Task TestInvalidTabsInMultiLineCommentsAsync()
183+
{
184+
var testCode =
185+
"\tpublic class Foo\r\n" +
186+
"\t{\r\n" +
187+
"\t\tpublic void Bar()\r\n" +
188+
"\t\t{\r\n" +
189+
"\t\t \t/*\r\n" +
190+
"\t\t\tComment\t\t1\r\n" +
191+
"\t \t\tComment 2\r\n" +
192+
" \t\t\t*/\r\n" +
193+
"\t\t}\r\n" +
194+
"\t}\r\n";
195+
196+
var fixedTestCode = @" public class Foo
197+
{
198+
public void Bar()
199+
{
200+
/*
201+
Comment 1
202+
Comment 2
203+
*/
204+
}
205+
}
206+
";
207+
208+
DiagnosticResult[] expected =
209+
{
210+
this.CSharpDiagnostic().WithLocation(1, 1),
211+
this.CSharpDiagnostic().WithLocation(2, 1),
212+
this.CSharpDiagnostic().WithLocation(3, 1),
213+
this.CSharpDiagnostic().WithLocation(4, 1),
214+
this.CSharpDiagnostic().WithLocation(5, 1),
215+
this.CSharpDiagnostic().WithLocation(5, 5),
216+
this.CSharpDiagnostic().WithLocation(9, 1),
217+
this.CSharpDiagnostic().WithLocation(10, 1),
218+
};
219+
220+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
221+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
222+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
223+
}
224+
100225
/// <inheritdoc/>
101226
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
102227
{

StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1027CodeFixProvider.cs

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
namespace StyleCop.Analyzers.SpacingRules
55
{
6+
using System.Collections.Generic;
67
using System.Collections.Immutable;
78
using System.Composition;
89
using System.Linq;
@@ -12,7 +13,6 @@ namespace StyleCop.Analyzers.SpacingRules
1213
using Microsoft.CodeAnalysis;
1314
using Microsoft.CodeAnalysis.CodeActions;
1415
using Microsoft.CodeAnalysis.CodeFixes;
15-
using Microsoft.CodeAnalysis.CSharp;
1616
using Microsoft.CodeAnalysis.Text;
1717
using StyleCop.Analyzers.Helpers;
1818

@@ -32,7 +32,7 @@ public class SA1027CodeFixProvider : CodeFixProvider
3232
/// <inheritdoc/>
3333
public override FixAllProvider GetFixAllProvider()
3434
{
35-
return CustomFixAllProviders.BatchFixer;
35+
return FixAll.Instance;
3636
}
3737

3838
/// <inheritdoc/>
@@ -49,53 +49,84 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
4949
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
5050
{
5151
var indentationOptions = IndentationOptions.FromDocument(document);
52-
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
53-
54-
var violatingTrivia = syntaxRoot.FindTrivia(diagnostic.Location.SourceSpan.Start);
55-
56-
var stringBuilder = new StringBuilder();
52+
SourceText sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
53+
return document.WithText(sourceText.WithChanges(FixDiagnostic(indentationOptions, sourceText, diagnostic)));
54+
}
5755

58-
int firstTriviaIndex = violatingTrivia.GetLineSpan().StartLinePosition.Character;
59-
string relevantText;
60-
if (firstTriviaIndex == 0)
61-
{
62-
relevantText = violatingTrivia.ToFullString();
63-
}
64-
else
65-
{
66-
SourceText sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
67-
relevantText = sourceText.ToString(new TextSpan(violatingTrivia.FullSpan.Start - firstTriviaIndex, firstTriviaIndex + violatingTrivia.FullSpan.Length));
68-
}
56+
private static TextChange FixDiagnostic(IndentationOptions indentationOptions, SourceText sourceText, Diagnostic diagnostic)
57+
{
58+
TextSpan span = diagnostic.Location.SourceSpan;
6959

60+
TextLine startLine = sourceText.Lines.GetLineFromPosition(span.Start);
61+
string text = sourceText.ToString(TextSpan.FromBounds(startLine.Start, span.End));
62+
StringBuilder replacement = new StringBuilder(indentationOptions.TabSize * span.Length);
7063
int column = 0;
71-
for (int i = 0; i < relevantText.Length; i++)
64+
for (int i = 0; i < text.Length; i++)
7265
{
73-
char c = relevantText[i];
66+
char c = text[i];
7467
if (c == '\t')
7568
{
7669
var offsetWithinTabColumn = column % indentationOptions.TabSize;
7770
var spaceCount = indentationOptions.TabSize - offsetWithinTabColumn;
7871

79-
if (i >= firstTriviaIndex)
72+
if (i >= span.Start - startLine.Start)
8073
{
81-
stringBuilder.Append(' ', spaceCount);
74+
replacement.Append(' ', spaceCount);
8275
}
8376

8477
column += spaceCount;
8578
}
8679
else
8780
{
88-
if (i >= firstTriviaIndex)
81+
if (i >= span.Start - startLine.Start)
8982
{
90-
stringBuilder.Append(c);
83+
replacement.Append(c);
9184
}
9285

93-
column++;
86+
if (c == '\n')
87+
{
88+
column = 0;
89+
}
90+
else
91+
{
92+
column++;
93+
}
9494
}
9595
}
9696

97-
var newSyntaxRoot = syntaxRoot.ReplaceTrivia(violatingTrivia, SyntaxFactory.Whitespace(stringBuilder.ToString()));
98-
return document.WithSyntaxRoot(newSyntaxRoot);
97+
return new TextChange(span, replacement.ToString());
98+
}
99+
100+
private class FixAll : DocumentBasedFixAllProvider
101+
{
102+
public static FixAllProvider Instance { get; }
103+
= new FixAll();
104+
105+
protected override string CodeActionTitle
106+
=> SpacingResources.SA1027CodeFix;
107+
108+
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document)
109+
{
110+
var diagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false);
111+
if (diagnostics.IsEmpty)
112+
{
113+
return null;
114+
}
115+
116+
var indentationOptions = IndentationOptions.FromDocument(document);
117+
SourceText sourceText = await document.GetTextAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
118+
119+
List<TextChange> changes = new List<TextChange>();
120+
foreach (var diagnostic in diagnostics)
121+
{
122+
changes.Add(FixDiagnostic(indentationOptions, sourceText, diagnostic));
123+
}
124+
125+
changes.Sort((left, right) => left.Span.Start.CompareTo(right.Span.Start));
126+
127+
SyntaxTree tree = await document.GetSyntaxTreeAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
128+
return await tree.WithChangedText(sourceText.WithChanges(changes)).GetRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
129+
}
99130
}
100131
}
101132
}

StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1027TabsMustNotBeUsed.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ private static void HandleSyntaxTree(SyntaxTreeAnalysisContext context)
6767
switch (trivia.Kind())
6868
{
6969
case SyntaxKind.WhitespaceTrivia:
70+
case SyntaxKind.DocumentationCommentExteriorTrivia:
71+
case SyntaxKind.SingleLineCommentTrivia:
72+
case SyntaxKind.MultiLineCommentTrivia:
7073
HandleWhitespaceTrivia(context, trivia);
7174
break;
7275

0 commit comments

Comments
 (0)