Skip to content

Commit 87f2fc4

Browse files
committed
Fix file header insertion before a blank line
Fixes #2415
1 parent 9166e4f commit 87f2fc4

2 files changed

Lines changed: 124 additions & 2 deletions

File tree

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

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ private static SyntaxNode ReplaceHeader(Document document, SyntaxNode root, Styl
211211
bool onBlankLine = false;
212212
bool inCopyright = isMalformedHeader;
213213
int? copyrightTriviaIndex = null;
214-
var removalList = new System.Collections.Generic.List<int>();
214+
var removalList = new List<int>();
215215
var leadingSpaces = string.Empty;
216216
string possibleLeadingSpaces = string.Empty;
217217

@@ -351,7 +351,34 @@ private static SyntaxNode AddHeader(Document document, SyntaxNode root, string n
351351
string newLineText = document.Project.Solution.Workspace.Options.GetOption(FormattingOptions.NewLine, LanguageNames.CSharp);
352352
var newLineTrivia = SyntaxFactory.EndOfLine(newLineText);
353353
var newTrivia = CreateNewHeader("//", name, settings, newLineText).Add(newLineTrivia).Add(newLineTrivia);
354-
newTrivia = newTrivia.AddRange(root.GetLeadingTrivia());
354+
355+
// Skip blank lines already at the beginning of the document, since we add our own
356+
SyntaxTriviaList leadingTrivia = root.GetLeadingTrivia();
357+
int skipCount = 0;
358+
for (int i = 0; i < leadingTrivia.Count; i++)
359+
{
360+
bool done = false;
361+
switch (leadingTrivia[i].Kind())
362+
{
363+
case SyntaxKind.WhitespaceTrivia:
364+
break;
365+
366+
case SyntaxKind.EndOfLineTrivia:
367+
skipCount = i + 1;
368+
break;
369+
370+
default:
371+
done = true;
372+
break;
373+
}
374+
375+
if (done)
376+
{
377+
break;
378+
}
379+
}
380+
381+
newTrivia = newTrivia.AddRange(leadingTrivia.RemoveRange(0, skipCount));
355382

356383
return root.WithLeadingTrivia(newTrivia);
357384
}

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

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,101 @@ public virtual async Task TestNoFileHeaderAsync()
5050
var fixedCode = @"// Copyright (c) FooCorp. All rights reserved.
5151
// Licensed under the ??? license. See LICENSE file in the project root for full license information.
5252
53+
namespace Foo
54+
{
55+
}
56+
";
57+
58+
var expectedDiagnostic = this.CSharpDiagnostic(FileHeaderAnalyzers.SA1633DescriptorMissing).WithLocation(1, 1);
59+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None).ConfigureAwait(false);
60+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
61+
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
62+
}
63+
64+
/// <summary>
65+
/// Verifies that the analyzer will report <see cref="FileHeaderAnalyzers.SA1633DescriptorMissing"/> for
66+
/// projects not using XML headers when the file is completely missing a header.
67+
/// </summary>
68+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
69+
[Fact]
70+
[WorkItem(2415, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2415")]
71+
public virtual async Task TestNoFileHeaderWithUsingDirectiveAsync()
72+
{
73+
var testCode = @"using System;
74+
75+
namespace Foo
76+
{
77+
}
78+
";
79+
var fixedCode = @"// Copyright (c) FooCorp. All rights reserved.
80+
// Licensed under the ??? license. See LICENSE file in the project root for full license information.
81+
82+
using System;
83+
84+
namespace Foo
85+
{
86+
}
87+
";
88+
89+
var expectedDiagnostic = this.CSharpDiagnostic(FileHeaderAnalyzers.SA1633DescriptorMissing).WithLocation(1, 1);
90+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None).ConfigureAwait(false);
91+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
92+
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
93+
}
94+
95+
/// <summary>
96+
/// Verifies that the analyzer will report <see cref="FileHeaderAnalyzers.SA1633DescriptorMissing"/> for
97+
/// projects not using XML headers when the file is completely missing a header.
98+
/// </summary>
99+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
100+
[Fact]
101+
[WorkItem(2415, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2415")]
102+
public virtual async Task TestNoFileHeaderWithBlankLineAndUsingDirectiveAsync()
103+
{
104+
var testCode = @"
105+
using System;
106+
107+
namespace Foo
108+
{
109+
}
110+
";
111+
var fixedCode = @"// Copyright (c) FooCorp. All rights reserved.
112+
// Licensed under the ??? license. See LICENSE file in the project root for full license information.
113+
114+
using System;
115+
116+
namespace Foo
117+
{
118+
}
119+
";
120+
121+
var expectedDiagnostic = this.CSharpDiagnostic(FileHeaderAnalyzers.SA1633DescriptorMissing).WithLocation(1, 1);
122+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None).ConfigureAwait(false);
123+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
124+
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
125+
}
126+
127+
/// <summary>
128+
/// Verifies that the analyzer will report <see cref="FileHeaderAnalyzers.SA1633DescriptorMissing"/> for
129+
/// projects not using XML headers when the file is completely missing a header.
130+
/// </summary>
131+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
132+
[Fact]
133+
[WorkItem(2415, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2415")]
134+
public virtual async Task TestNoFileHeaderWithWhitespaceLineAsync()
135+
{
136+
var testCode = " " + @"
137+
using System;
138+
139+
namespace Foo
140+
{
141+
}
142+
";
143+
var fixedCode = @"// Copyright (c) FooCorp. All rights reserved.
144+
// Licensed under the ??? license. See LICENSE file in the project root for full license information.
145+
146+
using System;
147+
53148
namespace Foo
54149
{
55150
}

0 commit comments

Comments
 (0)