diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/FileHeaderCodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/FileHeaderCodeFixProvider.cs index 4039ec724..da67e9b8c 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/FileHeaderCodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/FileHeaderCodeFixProvider.cs @@ -211,7 +211,7 @@ private static SyntaxNode ReplaceHeader(Document document, SyntaxNode root, Styl bool onBlankLine = false; bool inCopyright = isMalformedHeader; int? copyrightTriviaIndex = null; - var removalList = new System.Collections.Generic.List(); + var removalList = new List(); var leadingSpaces = string.Empty; string possibleLeadingSpaces = string.Empty; @@ -351,7 +351,34 @@ private static SyntaxNode AddHeader(Document document, SyntaxNode root, string n string newLineText = document.Project.Solution.Workspace.Options.GetOption(FormattingOptions.NewLine, LanguageNames.CSharp); var newLineTrivia = SyntaxFactory.EndOfLine(newLineText); var newTrivia = CreateNewHeader("//", name, settings, newLineText).Add(newLineTrivia).Add(newLineTrivia); - newTrivia = newTrivia.AddRange(root.GetLeadingTrivia()); + + // Skip blank lines already at the beginning of the document, since we add our own + SyntaxTriviaList leadingTrivia = root.GetLeadingTrivia(); + int skipCount = 0; + for (int i = 0; i < leadingTrivia.Count; i++) + { + bool done = false; + switch (leadingTrivia[i].Kind()) + { + case SyntaxKind.WhitespaceTrivia: + break; + + case SyntaxKind.EndOfLineTrivia: + skipCount = i + 1; + break; + + default: + done = true; + break; + } + + if (done) + { + break; + } + } + + newTrivia = newTrivia.AddRange(leadingTrivia.RemoveRange(0, skipCount)); return root.WithLeadingTrivia(newTrivia); } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/NoXmlFileHeaderUnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/NoXmlFileHeaderUnitTests.cs index 1b22a4ad6..a4f4e915f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/NoXmlFileHeaderUnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/NoXmlFileHeaderUnitTests.cs @@ -50,6 +50,101 @@ public virtual async Task TestNoFileHeaderAsync() var fixedCode = @"// Copyright (c) FooCorp. All rights reserved. // Licensed under the ??? license. See LICENSE file in the project root for full license information. +namespace Foo +{ +} +"; + + var expectedDiagnostic = this.CSharpDiagnostic(FileHeaderAnalyzers.SA1633DescriptorMissing).WithLocation(1, 1); + await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Verifies that the analyzer will report for + /// projects not using XML headers when the file is completely missing a header. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(2415, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2415")] + public virtual async Task TestNoFileHeaderWithUsingDirectiveAsync() + { + var testCode = @"using System; + +namespace Foo +{ +} +"; + var fixedCode = @"// Copyright (c) FooCorp. All rights reserved. +// Licensed under the ??? license. See LICENSE file in the project root for full license information. + +using System; + +namespace Foo +{ +} +"; + + var expectedDiagnostic = this.CSharpDiagnostic(FileHeaderAnalyzers.SA1633DescriptorMissing).WithLocation(1, 1); + await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Verifies that the analyzer will report for + /// projects not using XML headers when the file is completely missing a header. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(2415, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2415")] + public virtual async Task TestNoFileHeaderWithBlankLineAndUsingDirectiveAsync() + { + var testCode = @" +using System; + +namespace Foo +{ +} +"; + var fixedCode = @"// Copyright (c) FooCorp. All rights reserved. +// Licensed under the ??? license. See LICENSE file in the project root for full license information. + +using System; + +namespace Foo +{ +} +"; + + var expectedDiagnostic = this.CSharpDiagnostic(FileHeaderAnalyzers.SA1633DescriptorMissing).WithLocation(1, 1); + await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Verifies that the analyzer will report for + /// projects not using XML headers when the file is completely missing a header. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(2415, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2415")] + public virtual async Task TestNoFileHeaderWithWhitespaceLineAsync() + { + var testCode = " " + @" +using System; + +namespace Foo +{ +} +"; + var fixedCode = @"// Copyright (c) FooCorp. All rights reserved. +// Licensed under the ??? license. See LICENSE file in the project root for full license information. + +using System; + namespace Foo { }