Skip to content

Commit 9201b20

Browse files
committed
Add the newlineAtEndOfFile configuration property
This option allows users to configure the way SA1518 treats newline characters at the end of a file. Fixes #1800
1 parent 784cfff commit 9201b20

10 files changed

Lines changed: 313 additions & 49 deletions

File tree

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

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ namespace StyleCop.Analyzers.LayoutRules
55
{
66
using System.Collections.Immutable;
77
using System.Composition;
8-
using System.Linq;
98
using System.Threading;
109
using System.Threading.Tasks;
1110
using Helpers;
1211
using Microsoft.CodeAnalysis;
1312
using Microsoft.CodeAnalysis.CodeActions;
1413
using Microsoft.CodeAnalysis.CodeFixes;
15-
using Microsoft.CodeAnalysis.CSharp;
14+
using Microsoft.CodeAnalysis.Text;
15+
using Settings.ObjectModel;
1616

1717
/// <summary>
1818
/// Implements a code fix for <see cref="SA1518CodeMustNotContainBlankLinesAtEndOfFile"/>.
@@ -34,44 +34,55 @@ public override FixAllProvider GetFixAllProvider()
3434
/// <inheritdoc/>
3535
public override Task RegisterCodeFixesAsync(CodeFixContext context)
3636
{
37-
foreach (Diagnostic diagnostic in context.Diagnostics)
37+
var settings = SettingsHelper.GetStyleCopSettings(context.Document.Project.AnalyzerOptions, context.CancellationToken);
38+
foreach (var diagnostic in context.Diagnostics)
3839
{
3940
context.RegisterCodeFix(
4041
CodeAction.Create(
4142
LayoutResources.SA1518CodeFix,
42-
cancellationToken => GetTransformedDocumentAsync(context.Document, cancellationToken),
43+
cancellationToken => FixEndOfFileAsync(context.Document, diagnostic, settings.LayoutRules.NewlineAtEndOfFile, cancellationToken),
4344
nameof(SA1518CodeFixProvider)),
4445
diagnostic);
4546
}
4647

4748
return SpecializedTasks.CompletedTask;
4849
}
4950

50-
private static async Task<Document> GetTransformedDocumentAsync(Document document, CancellationToken token)
51+
/// <summary>
52+
/// Fixes the whitespace at the end of a document.
53+
/// </summary>
54+
/// <param name="document">The document to be changed.</param>
55+
/// <param name="diagnostic">The diagnostic to fix.</param>
56+
/// <param name="newlineAtEndOfFile">A <see cref="EndOfFileHandling"/> value indicating the desired behavior.</param>
57+
/// <param name="cancellationToken">The cancellation token associated with the fix action.</param>
58+
/// <returns>The transformed document.</returns>
59+
private static async Task<Document> FixEndOfFileAsync(Document document, Diagnostic diagnostic, EndOfFileHandling newlineAtEndOfFile, CancellationToken cancellationToken)
5160
{
52-
var syntaxRoot = await document.GetSyntaxRootAsync(token).ConfigureAwait(false);
53-
54-
var lastToken = syntaxRoot.GetLastToken(includeZeroWidth: true);
55-
56-
var newLastToken = StripViolatingWhitespace(lastToken);
57-
var newSyntaxRoot = syntaxRoot.ReplaceToken(lastToken, newLastToken);
58-
var newDocument = document.WithSyntaxRoot(newSyntaxRoot);
59-
60-
return newDocument;
61+
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
62+
string replacement = newlineAtEndOfFile == EndOfFileHandling.Omit ? string.Empty : "\r\n";
63+
return document.WithText(text.WithChanges(new TextChange(diagnostic.Location.SourceSpan, replacement)));
6164
}
6265

63-
private static SyntaxToken StripViolatingWhitespace(SyntaxToken token)
66+
private class FixAll : DocumentBasedFixAllProvider
6467
{
65-
SyntaxToken result = token;
68+
public static FixAllProvider Instance { get; } =
69+
new FixAll();
6670

67-
var trailingWhitespaceIndex = TriviaHelper.IndexOfTrailingWhitespace(token.LeadingTrivia);
68-
if (trailingWhitespaceIndex != -1)
71+
protected override string CodeActionTitle =>
72+
LayoutResources.SA1518CodeFix;
73+
74+
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document)
6975
{
70-
var newTriviaList = SyntaxFactory.TriviaList(token.LeadingTrivia.Take(trailingWhitespaceIndex));
71-
result = token.WithLeadingTrivia(newTriviaList);
72-
}
76+
var diagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false);
77+
if (diagnostics.IsEmpty)
78+
{
79+
return null;
80+
}
7381

74-
return result;
82+
var settings = SettingsHelper.GetStyleCopSettings(document.Project.AnalyzerOptions, fixAllContext.CancellationToken);
83+
Document updatedDocument = await FixEndOfFileAsync(document, diagnostics[0], settings.LayoutRules.NewlineAtEndOfFile, fixAllContext.CancellationToken).ConfigureAwait(false);
84+
return await updatedDocument.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
85+
}
7586
}
7687
}
7788
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1518UnitTests.cs

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,12 @@ public void Bar(int i)
3434
public async Task TestWithBlankLinesAtEndOfFileAsync()
3535
{
3636
var testCode = BaseCode + "\r\n\r\n";
37-
await this.VerifyCSharpDiagnosticAsync(testCode, this.GenerateExpectedWarning(9, 1), CancellationToken.None).ConfigureAwait(false);
37+
var fixedCode = BaseCode + "\r\n";
38+
39+
var expected = this.CSharpDiagnostic().WithLocation(8, 2);
40+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
41+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
42+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
3843
}
3944

4045
/// <summary>
@@ -45,7 +50,12 @@ public async Task TestWithBlankLinesAtEndOfFileAsync()
4550
public async Task TestWithLineFeedOnlyBlankLinesAtEndOfFileAsync()
4651
{
4752
var testCode = BaseCode + "\n\n";
48-
await this.VerifyCSharpDiagnosticAsync(testCode, this.GenerateExpectedWarning(9, 1), CancellationToken.None).ConfigureAwait(false);
53+
var fixedCode = BaseCode + "\r\n";
54+
55+
var expected = this.CSharpDiagnostic().WithLocation(8, 2);
56+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
57+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
58+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
4959
}
5060

5161
/// <summary>
@@ -88,7 +98,12 @@ public async Task TestWithoutCarriageReturnLineFeedAtEndOfFileAsync()
8898
public async Task TestFileEndsWithSpacesAsync()
8999
{
90100
var testCode = BaseCode + "\r\n ";
91-
await this.VerifyCSharpDiagnosticAsync(testCode, this.GenerateExpectedWarning(9, 1), CancellationToken.None).ConfigureAwait(false);
101+
var fixedCode = BaseCode + "\r\n";
102+
103+
var expected = this.CSharpDiagnostic().WithLocation(8, 2);
104+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
105+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
106+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
92107
}
93108

94109
/// <summary>
@@ -110,7 +125,12 @@ public async Task TestFileEndingWithCommentAsync()
110125
public async Task TestFileEndingWithCommentAndSpuriousWhitespaceAsync()
111126
{
112127
var testCode = BaseCode + "\r\n// Test comment\r\n \r\n";
113-
await this.VerifyCSharpDiagnosticAsync(testCode, this.GenerateExpectedWarning(10, 1), CancellationToken.None).ConfigureAwait(false);
128+
var fixedCode = BaseCode + "\r\n// Test comment\r\n";
129+
130+
var expected = this.CSharpDiagnostic().WithLocation(9, 16);
131+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
132+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
133+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
114134
}
115135

116136
/// <summary>
@@ -132,7 +152,12 @@ public async Task TestFileEndingWithEndIfAsync()
132152
public async Task TestFileEndingWithEndIfWithSpuriousWhitespaceAsync()
133153
{
134154
var testCode = "#if true\r\n" + BaseCode + "\r\n#endif\r\n \r\n";
135-
await this.VerifyCSharpDiagnosticAsync(testCode, this.GenerateExpectedWarning(11, 1), CancellationToken.None).ConfigureAwait(false);
155+
var fixedCode = "#if true\r\n" + BaseCode + "\r\n#endif\r\n";
156+
157+
var expected = this.CSharpDiagnostic().WithLocation(10, 7);
158+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
159+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
160+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
136161
}
137162

138163
/// <summary>
@@ -144,9 +169,12 @@ public async Task TestFileEndingWithEndIfWithSpuriousWhitespaceAsync()
144169
public async Task TestCodeFixProviderStripsTrailingBlankLinesAsync()
145170
{
146171
var testCode = BaseCode + "\r\n\r\n";
147-
var fixedTestCode = BaseCode + "\r\n";
172+
var fixedCode = BaseCode + "\r\n";
148173

149-
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
174+
var expected = this.CSharpDiagnostic().WithLocation(8, 2);
175+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
176+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
177+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
150178
}
151179

152180
/// <summary>
@@ -158,9 +186,12 @@ public async Task TestCodeFixProviderStripsTrailingBlankLinesAsync()
158186
public async Task TestCodeFixProviderStripsTrailingBlankLinesIncludingWhitespaceAsync()
159187
{
160188
var testCode = BaseCode + "\r\n \r\n \r\n";
161-
var fixedTestCode = BaseCode + "\r\n";
189+
var fixedCode = BaseCode + "\r\n";
162190

163-
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
191+
var expected = this.CSharpDiagnostic().WithLocation(8, 2);
192+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
193+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
194+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
164195
}
165196

166197
/// <summary>
@@ -172,9 +203,12 @@ public async Task TestCodeFixProviderStripsTrailingBlankLinesIncludingWhitespace
172203
public async Task TestCodeFixProviderStripsTrailingLinefeedOnlyBlankLinesIncludingWhitespaceAsync()
173204
{
174205
var testCode = BaseCode + "\n \n \n";
175-
var fixedTestCode = BaseCode + "\n";
206+
var fixedCode = BaseCode + "\r\n";
176207

177-
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
208+
var expected = this.CSharpDiagnostic().WithLocation(8, 2);
209+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
210+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
211+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
178212
}
179213

180214
/// <summary>
@@ -186,9 +220,12 @@ public async Task TestCodeFixProviderStripsTrailingLinefeedOnlyBlankLinesIncludi
186220
public async Task TestCodeFixProviderOnlyStripsTrailingBlankLinesAsync()
187221
{
188222
var testCode = "#if true\r\n" + BaseCode + "\r\n#endif\r\n \r\n";
189-
var fixedTestCode = "#if true\r\n" + BaseCode + "\r\n#endif\r\n";
223+
var fixedCode = "#if true\r\n" + BaseCode + "\r\n#endif\r\n";
190224

191-
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
225+
var expected = this.CSharpDiagnostic().WithLocation(10, 7);
226+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
227+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
228+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
192229
}
193230

194231
/// <inheritdoc/>
@@ -202,10 +239,5 @@ protected override CodeFixProvider GetCSharpCodeFixProvider()
202239
{
203240
return new SA1518CodeFixProvider();
204241
}
205-
206-
private DiagnosticResult GenerateExpectedWarning(int line, int column)
207-
{
208-
return this.CSharpDiagnostic().WithLocation(line, column);
209-
}
210242
}
211243
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/Settings/SettingsUnitTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ public void VerifySettingsDefaults()
3232
Assert.NotNull(styleCopSettings.OrderingRules);
3333
Assert.Equal(UsingDirectivesPlacement.InsideNamespace, styleCopSettings.OrderingRules.UsingDirectivesPlacement);
3434

35+
Assert.NotNull(styleCopSettings.LayoutRules);
36+
Assert.Equal(EndOfFileHandling.Allow, styleCopSettings.LayoutRules.NewlineAtEndOfFile);
37+
3538
Assert.NotNull(styleCopSettings.SpacingRules);
3639
Assert.NotNull(styleCopSettings.ReadabilityRules);
3740
Assert.NotNull(styleCopSettings.MaintainabilityRules);

0 commit comments

Comments
 (0)