Skip to content

Commit bdf42f3

Browse files
committed
Merge pull request #1791 from vweijsters/fix-1781
SA1633 now accepts leading whitespace (incl. end-of-line)
2 parents a16c309 + 877458f commit bdf42f3

3 files changed

Lines changed: 90 additions & 5 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1633UnitTests.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@ namespace StyleCop.Analyzers.Test.DocumentationRules
1414
/// </summary>
1515
public class SA1633UnitTests : FileHeaderTestBase
1616
{
17+
private const string NoXmlMultiLineHeaderTestSettings = @"
18+
{
19+
""settings"": {
20+
""documentationRules"": {
21+
""companyName"": ""FooCorp"",
22+
""copyrightText"": ""copyright (c) {companyName}. All rights reserved."",
23+
""xmlHeader"": false
24+
}
25+
}
26+
}
27+
";
28+
29+
private bool useNoXmlSettings;
30+
1731
/// <summary>
1832
/// Verifies that the analyzer will report <see cref="FileHeaderAnalyzers.SA1633DescriptorMissing"/> for
1933
/// projects using XML headers (the default) when the file is completely missing a header.
@@ -241,6 +255,62 @@ namespace Foo
241255
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
242256
}
243257

258+
/// <summary>
259+
/// Verifies that blank lines before a valid XML header will not produce a diagnostic message.
260+
/// This is a regression test for #1781.
261+
/// </summary>
262+
/// <param name="prefix">The string to add before the header.</param>
263+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
264+
[Theory]
265+
[InlineData(" ")]
266+
[InlineData("\t\t")]
267+
[InlineData(" \t")]
268+
[InlineData(" \r\n\t\r\n")]
269+
[InlineData("\r\n")]
270+
[InlineData("\r\n\r\n")]
271+
public async Task TestValidXmlFileHeaderWithLeadingBlankLinesAsync(string prefix)
272+
{
273+
var testCode = $@"{prefix}// <copyright file=""Test0.cs"" company=""FooCorp"">
274+
// Copyright (c) FooCorp. All rights reserved.
275+
// </copyright>
276+
";
277+
278+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
279+
}
280+
281+
/// <summary>
282+
/// Verifies that blank lines before a valid header will not produce a diagnostic message.
283+
/// This is a regression test for #1781.
284+
/// </summary>
285+
/// <param name="prefix">The string to add before the header.</param>
286+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
287+
[Theory]
288+
[InlineData(" ")]
289+
[InlineData("\t\t")]
290+
[InlineData(" \t")]
291+
[InlineData(" \r\n\t\r\n")]
292+
[InlineData("\r\n")]
293+
[InlineData("\r\n\r\n")]
294+
public async Task TestValidFileHeaderWithLeadingBlankLinesAsync(string prefix)
295+
{
296+
this.useNoXmlSettings = true;
297+
var testCode = $@"{prefix}// copyright (c) FooCorp. All rights reserved.
298+
";
299+
300+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
301+
}
302+
303+
/// <inheritdoc/>
304+
protected override string GetSettings()
305+
{
306+
if (this.useNoXmlSettings)
307+
{
308+
return NoXmlMultiLineHeaderTestSettings;
309+
}
310+
311+
return base.GetSettings();
312+
}
313+
244314
/// <inheritdoc/>
245315
protected override CodeFixProvider GetCSharpCodeFixProvider()
246316
{

StyleCop.Analyzers/StyleCop.Analyzers/Helpers/FileHeaderHelpers.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ internal static class FileHeaderHelpers
2525
internal static FileHeader ParseFileHeader(SyntaxNode root)
2626
{
2727
var firstToken = root.GetFirstToken(includeZeroWidth: true);
28+
var firstNonWhitespaceTrivia = TriviaHelper.IndexOfFirstNonWhitespaceTrivia(firstToken.LeadingTrivia, true);
2829

29-
if (!firstToken.HasLeadingTrivia)
30+
if (firstNonWhitespaceTrivia == -1)
3031
{
3132
return FileHeader.MissingFileHeader;
3233
}
@@ -37,8 +38,7 @@ internal static FileHeader ParseFileHeader(SyntaxNode root)
3738
var fileHeaderStart = int.MaxValue;
3839
var fileHeaderEnd = int.MinValue;
3940

40-
int i;
41-
for (i = 0; !done && (i < firstToken.LeadingTrivia.Count); i++)
41+
for (var i = firstNonWhitespaceTrivia; !done && (i < firstToken.LeadingTrivia.Count); i++)
4242
{
4343
var trivia = firstToken.LeadingTrivia[i];
4444

@@ -119,7 +119,7 @@ internal static XmlFileHeader ParseXmlFileHeader(SyntaxNode root)
119119
int fileHeaderStart;
120120
int fileHeaderEnd;
121121

122-
var firstNonWhitespaceTrivia = TriviaHelper.IndexOfFirstNonWhitespaceTrivia(firstToken.LeadingTrivia, false);
122+
var firstNonWhitespaceTrivia = TriviaHelper.IndexOfFirstNonWhitespaceTrivia(firstToken.LeadingTrivia, true);
123123
if (firstNonWhitespaceTrivia == -1)
124124
{
125125
return XmlFileHeader.MissingFileHeader;

documentation/SA1633.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ A C# code file is missing a standard file header.
2121

2222
## Rule description
2323

24-
A violation of this rule occurs when a C# source file is missing a file header. The file header must begin on the first line of the file, and must be formatted as a block of comments containing Xml, as follows:
24+
A violation of this rule occurs when a C# source file is missing a file header.
25+
26+
The file header must begin at the start of the file, and it may only be preceded by whitespace.
27+
28+
The file header must be formatted as a block of comments containing either Xml or preconfigured text, as follows:
2529

2630
```csharp
2731
//-----------------------------------------------------------------------
@@ -31,6 +35,17 @@ A violation of this rule occurs when a C# source file is missing a file header.
3135
//-----------------------------------------------------------------------
3236
```
3337

38+
or
39+
40+
```csharp
41+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
42+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
43+
```
44+
45+
The type of header to use depends on the setting of the `xmlHeader` property. See the [configuration](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/Configuration.md) section for details on how to set this up.
46+
47+
### Examples
48+
3449
For example, a file called Widget.cs from a fictional company called Sprocket Enterprises should contain a file header similar to the following:
3550

3651
```csharp

0 commit comments

Comments
 (0)