Skip to content

Commit 2a495dc

Browse files
committed
Fix handling of line endings in FileHeaderCodeFixProvider
Fixes #1437
1 parent 526ebb0 commit 2a495dc

2 files changed

Lines changed: 71 additions & 30 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/NoXmlFileHeaderUnitTests.cs

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ namespace StyleCop.Analyzers.Test.DocumentationRules
77
using System.Threading;
88
using System.Threading.Tasks;
99
using Analyzers.DocumentationRules;
10-
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CodeFixes;
1111
using Microsoft.CodeAnalysis.Diagnostics;
1212
using TestHelper;
1313
using Xunit;
1414

1515
/// <summary>
1616
/// Unit tests for file header that do not follow the XML syntax.
1717
/// </summary>
18-
public class NoXmlFileHeaderUnitTests : DiagnosticVerifier
18+
public class NoXmlFileHeaderUnitTests : CodeFixVerifier
1919
{
2020
private const string SettingsFileName = "stylecop.json";
2121
private const string TestSettings = @"
@@ -45,10 +45,19 @@ public virtual async Task TestNoFileHeaderAsync()
4545
var testCode = @"namespace Foo
4646
{
4747
}
48+
";
49+
var fixedCode = @"// Copyright (c) FooCorp. All rights reserved.
50+
// Licensed under the ??? license. See LICENSE file in the project root for full license information.
51+
52+
namespace Foo
53+
{
54+
}
4855
";
4956

5057
var expectedDiagnostic = this.CSharpDiagnostic(FileHeaderAnalyzers.SA1633DescriptorMissing).WithLocation(1, 1);
5158
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None).ConfigureAwait(false);
59+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
60+
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
5261
}
5362

5463
/// <summary>
@@ -87,13 +96,13 @@ namespace Bar
8796
}
8897

8998
/// <summary>
90-
/// Verifies that a valid file header built using multi line comments will not produce a diagnostic message.
99+
/// Verifies that a valid file header built using multi-line comments will not produce a diagnostic message.
91100
/// </summary>
92101
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
93102
[Fact]
94-
public async Task TestValidFileHeaderWithMultiLineCommentsAsync()
103+
public async Task TestValidFileHeaderWithMultiLineComments1Async()
95104
{
96-
var testCodeFormat1 = @"/* Copyright (c) FooCorp. All rights reserved.
105+
var testCode = @"/* Copyright (c) FooCorp. All rights reserved.
97106
* Licensed under the ??? license. See LICENSE file in the project root for full license information.
98107
*/
99108
@@ -102,42 +111,55 @@ namespace Bar
102111
}
103112
";
104113

105-
var testCodeFormat2 = @"/* Copyright (c) FooCorp. All rights reserved.
114+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
115+
}
116+
117+
/// <summary>
118+
/// Verifies that a valid file header built using multi-line comments will not produce a diagnostic message.
119+
/// </summary>
120+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
121+
[Fact]
122+
public async Task TestValidFileHeaderWithMultiLineComments2Async()
123+
{
124+
var testCode = @"/* Copyright (c) FooCorp. All rights reserved.
106125
Licensed under the ??? license. See LICENSE file in the project root for full license information. */
107126
108127
namespace Bar
109128
{
110129
}
111130
";
112131

113-
await this.VerifyCSharpDiagnosticAsync(testCodeFormat1, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
114-
await this.VerifyCSharpDiagnosticAsync(testCodeFormat2, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
132+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
115133
}
116134

117135
/// <summary>
118136
/// Verifies that a file header without text / only whitespace will produce the expected diagnostic message.
119137
/// </summary>
138+
/// <param name="comment">The comment text.</param>
120139
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
121-
[Fact]
122-
public async Task TestInvalidFileHeaderWithoutTextAsync()
140+
[Theory]
141+
[InlineData("//")]
142+
[InlineData("// ")]
143+
public async Task TestInvalidFileHeaderWithoutTextAsync(string comment)
123144
{
124-
var testCodeFormat1 = @"//
145+
var testCode = $@"{comment}
125146
126147
namespace Bar
127-
{
128-
}
148+
{{
149+
}}
129150
";
130-
131-
var testCodeFormat2 = "// " + @"
151+
var fixedCode = @"// Copyright (c) FooCorp. All rights reserved.
152+
// Licensed under the ??? license. See LICENSE file in the project root for full license information.
132153
133154
namespace Bar
134155
{
135156
}
136157
";
137158

138159
var expected = this.CSharpDiagnostic(FileHeaderAnalyzers.SA1635Descriptor).WithLocation(1, 1);
139-
await this.VerifyCSharpDiagnosticAsync(testCodeFormat1, expected, CancellationToken.None).ConfigureAwait(false);
140-
await this.VerifyCSharpDiagnosticAsync(testCodeFormat2, expected, CancellationToken.None).ConfigureAwait(false);
160+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
161+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
162+
////await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
141163
}
142164

143165
/// <summary>
@@ -150,12 +172,21 @@ public async Task TestInvalidFileHeaderWithWrongTextAsync()
150172
var testCode = @"// Copyright (c) BarCorp. All rights reserved.
151173
// Licensed under the ??? license. See LICENSE file in the project root for full license information.
152174
175+
namespace Bar
176+
{
177+
}
178+
";
179+
var fixedCode = @"// Copyright (c) FooCorp. All rights reserved.
180+
// Licensed under the ??? license. See LICENSE file in the project root for full license information.
181+
153182
namespace Bar
154183
{
155184
}
156185
";
157186
var expected = this.CSharpDiagnostic(FileHeaderAnalyzers.SA1636Descriptor).WithLocation(1, 1);
158187
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
188+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
189+
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
159190
}
160191

161192
/// <inheritdoc/>
@@ -169,5 +200,11 @@ protected sealed override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAna
169200
{
170201
yield return new FileHeaderAnalyzers();
171202
}
203+
204+
/// <inheritdoc/>
205+
protected override CodeFixProvider GetCSharpCodeFixProvider()
206+
{
207+
return new FileHeaderCodeFixProvider();
208+
}
172209
}
173210
}

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/FileHeaderCodeFixProvider.cs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace StyleCop.Analyzers.DocumentationRules
1212
using Microsoft.CodeAnalysis.CodeActions;
1313
using Microsoft.CodeAnalysis.CodeFixes;
1414
using Microsoft.CodeAnalysis.CSharp;
15+
using Microsoft.CodeAnalysis.Formatting;
1516
using StyleCop.Analyzers.Helpers;
1617
using StyleCop.Analyzers.Settings.ObjectModel;
1718

@@ -54,7 +55,7 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen
5455
var settings = document.Project.AnalyzerOptions.GetStyleCopSettings();
5556

5657
var fileHeader = FileHeaderHelpers.ParseFileHeader(root);
57-
var newSyntaxRoot = fileHeader.IsMissing ? AddHeader(root, document.Name, settings) : ReplaceHeader(document, root, settings);
58+
var newSyntaxRoot = fileHeader.IsMissing ? AddHeader(document, root, document.Name, settings) : ReplaceHeader(document, root, settings);
5859

5960
return document.WithSyntaxRoot(newSyntaxRoot);
6061
}
@@ -104,36 +105,39 @@ private static SyntaxNode ReplaceHeader(Document document, SyntaxNode root, Styl
104105
}
105106
}
106107

107-
return root.WithLeadingTrivia(CreateNewHeader(document.Name, settings).Add(SyntaxFactory.CarriageReturnLineFeed).Add(SyntaxFactory.CarriageReturnLineFeed).AddRange(trivia));
108+
string newLineText = document.Project.Solution.Workspace.Options.GetOption(FormattingOptions.NewLine, LanguageNames.CSharp);
109+
return root.WithLeadingTrivia(CreateNewHeader(document.Name, settings, newLineText).Add(SyntaxFactory.CarriageReturnLineFeed).Add(SyntaxFactory.CarriageReturnLineFeed).AddRange(trivia));
108110
}
109111

110-
private static SyntaxNode AddHeader(SyntaxNode root, string name, StyleCopSettings settings)
112+
private static SyntaxNode AddHeader(Document document, SyntaxNode root, string name, StyleCopSettings settings)
111113
{
112-
var newTrivia = CreateNewHeader(name, settings).Add(SyntaxFactory.CarriageReturnLineFeed).Add(SyntaxFactory.CarriageReturnLineFeed);
114+
string newLineText = document.Project.Solution.Workspace.Options.GetOption(FormattingOptions.NewLine, LanguageNames.CSharp);
115+
var newTrivia = CreateNewHeader(name, settings, newLineText).Add(SyntaxFactory.CarriageReturnLineFeed).Add(SyntaxFactory.CarriageReturnLineFeed);
113116
newTrivia = newTrivia.AddRange(root.GetLeadingTrivia());
114117

115118
return root.WithLeadingTrivia(newTrivia);
116119
}
117120

118-
private static SyntaxTriviaList CreateNewHeader(string filename, StyleCopSettings settings)
121+
private static SyntaxTriviaList CreateNewHeader(string filename, StyleCopSettings settings, string newLineText)
119122
{
120-
var copyrightText = "// " + GetCopyrightText(settings.DocumentationRules.CopyrightText);
123+
var copyrightText = "// " + GetCopyrightText(settings.DocumentationRules.CopyrightText, newLineText);
121124
var newHeader = settings.DocumentationRules.XmlHeader
122-
? WrapInXmlComment(copyrightText, filename, settings)
125+
? WrapInXmlComment(copyrightText, filename, settings, newLineText)
123126
: copyrightText;
124127
return SyntaxFactory.ParseLeadingTrivia(newHeader);
125128
}
126129

127-
private static string WrapInXmlComment(string copyrightText, string filename, StyleCopSettings settings)
130+
private static string WrapInXmlComment(string copyrightText, string filename, StyleCopSettings settings, string newLineText)
128131
{
129-
return $@"// <copyright file=""{filename}"" company=""{settings.DocumentationRules.CompanyName}"">
130-
{copyrightText}
131-
// </copyright>";
132+
return
133+
$"// <copyright file=\"{filename}\" company=\"{settings.DocumentationRules.CompanyName}\">" + newLineText
134+
+ copyrightText + newLineText
135+
+ "// </copyright>";
132136
}
133137

134-
private static string GetCopyrightText(string copyrightText)
138+
private static string GetCopyrightText(string copyrightText, string newLineText)
135139
{
136-
return string.Join("\n// ", copyrightText.Split('\n'));
140+
return string.Join(newLineText + "// ", copyrightText.Split('\n'));
137141
}
138142
}
139143
}

0 commit comments

Comments
 (0)