diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/NoXmlFileHeaderUnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/NoXmlFileHeaderUnitTests.cs index 04f0ae2ee..056a66615 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/NoXmlFileHeaderUnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/NoXmlFileHeaderUnitTests.cs @@ -7,7 +7,7 @@ namespace StyleCop.Analyzers.Test.DocumentationRules using System.Threading; using System.Threading.Tasks; using Analyzers.DocumentationRules; - using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; using TestHelper; using Xunit; @@ -15,7 +15,7 @@ namespace StyleCop.Analyzers.Test.DocumentationRules /// /// Unit tests for file header that do not follow the XML syntax. /// - public class NoXmlFileHeaderUnitTests : DiagnosticVerifier + public class NoXmlFileHeaderUnitTests : CodeFixVerifier { private const string SettingsFileName = "stylecop.json"; private const string TestSettings = @" @@ -45,10 +45,19 @@ public virtual async Task TestNoFileHeaderAsync() var testCode = @"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. + +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); } /// @@ -87,13 +96,13 @@ namespace Bar } /// - /// Verifies that a valid file header built using multi line comments will not produce a diagnostic message. + /// Verifies that a valid file header built using multi-line comments will not produce a diagnostic message. /// /// A representing the asynchronous unit test. [Fact] - public async Task TestValidFileHeaderWithMultiLineCommentsAsync() + public async Task TestValidFileHeaderWithMultiLineComments1Async() { - var testCodeFormat1 = @"/* Copyright (c) FooCorp. All rights reserved. + var testCode = @"/* Copyright (c) FooCorp. All rights reserved. * Licensed under the ??? license. See LICENSE file in the project root for full license information. */ @@ -102,7 +111,17 @@ namespace Bar } "; - var testCodeFormat2 = @"/* Copyright (c) FooCorp. All rights reserved. + await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Verifies that a valid file header built using multi-line comments will not produce a diagnostic message. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestValidFileHeaderWithMultiLineComments2Async() + { + var testCode = @"/* Copyright (c) FooCorp. All rights reserved. Licensed under the ??? license. See LICENSE file in the project root for full license information. */ namespace Bar @@ -110,25 +129,27 @@ namespace Bar } "; - await this.VerifyCSharpDiagnosticAsync(testCodeFormat1, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); - await this.VerifyCSharpDiagnosticAsync(testCodeFormat2, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } /// /// Verifies that a file header without text / only whitespace will produce the expected diagnostic message. /// + /// The comment text. /// A representing the asynchronous unit test. - [Fact] - public async Task TestInvalidFileHeaderWithoutTextAsync() + [Theory] + [InlineData("//")] + [InlineData("// ")] + public async Task TestInvalidFileHeaderWithoutTextAsync(string comment) { - var testCodeFormat1 = @"// + var testCode = $@"{comment} namespace Bar -{ -} +{{ +}} "; - - var testCodeFormat2 = "// " + @" + var fixedCode = @"// Copyright (c) FooCorp. All rights reserved. +// Licensed under the ??? license. See LICENSE file in the project root for full license information. namespace Bar { @@ -136,8 +157,9 @@ namespace Bar "; var expected = this.CSharpDiagnostic(FileHeaderAnalyzers.SA1635Descriptor).WithLocation(1, 1); - await this.VerifyCSharpDiagnosticAsync(testCodeFormat1, expected, CancellationToken.None).ConfigureAwait(false); - await this.VerifyCSharpDiagnosticAsync(testCodeFormat2, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false); } /// @@ -150,12 +172,21 @@ public async Task TestInvalidFileHeaderWithWrongTextAsync() var testCode = @"// Copyright (c) BarCorp. All rights reserved. // Licensed under the ??? license. See LICENSE file in the project root for full license information. +namespace Bar +{ +} +"; + var fixedCode = @"// Copyright (c) FooCorp. All rights reserved. +// Licensed under the ??? license. See LICENSE file in the project root for full license information. + namespace Bar { } "; var expected = this.CSharpDiagnostic(FileHeaderAnalyzers.SA1636Descriptor).WithLocation(1, 1); await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false); } /// @@ -169,5 +200,11 @@ protected sealed override IEnumerable GetCSharpDiagnosticAna { yield return new FileHeaderAnalyzers(); } + + /// + protected override CodeFixProvider GetCSharpCodeFixProvider() + { + return new FileHeaderCodeFixProvider(); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1635UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1635UnitTests.cs index 8d5a8800f..3b6b7683b 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1635UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1635UnitTests.cs @@ -23,6 +23,14 @@ public async Task TestFileHeaderWithShorthandCopyrightAsync() { var testCode = @"// +namespace Bar +{ +} +"; + var fixedCode = @"// +// Copyright (c) FooCorp. All rights reserved. +// + namespace Bar { } @@ -30,6 +38,8 @@ namespace Bar var expectedDiagnostic = this.CSharpDiagnostic(FileHeaderAnalyzers.SA1635Descriptor).WithLocation(1, 4); 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); } /// @@ -47,14 +57,25 @@ public async Task TestFileHeaderWithWhitespaceOnlyCopyrightAsync() "namespace Bar\r\n" + "{\r\n" + "}\r\n"; + string fixedCode = + "// \r\n" + + "// Copyright (c) FooCorp. All rights reserved.\r\n" + + "// \r\n" + + "\r\n" + + "namespace Bar\r\n" + + "{\r\n" + + "}\r\n"; var expectedDiagnostic = this.CSharpDiagnostic(FileHeaderAnalyzers.SA1635Descriptor).WithLocation(1, 4); 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); } + /// protected override CodeFixProvider GetCSharpCodeFixProvider() { - throw new System.NotImplementedException(); + return new FileHeaderCodeFixProvider(); } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/FileHeaderCodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/FileHeaderCodeFixProvider.cs index d26728dfd..b73357104 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/FileHeaderCodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/FileHeaderCodeFixProvider.cs @@ -12,6 +12,7 @@ namespace StyleCop.Analyzers.DocumentationRules using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.Formatting; using StyleCop.Analyzers.Helpers; using StyleCop.Analyzers.Settings.ObjectModel; @@ -29,6 +30,7 @@ public class FileHeaderCodeFixProvider : CodeFixProvider public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create( FileHeaderAnalyzers.SA1633DescriptorMissing.Id, + FileHeaderAnalyzers.SA1635Descriptor.Id, FileHeaderAnalyzers.SA1636Descriptor.Id); /// @@ -54,7 +56,7 @@ private static async Task GetTransformedDocumentAsync(Document documen var settings = document.Project.AnalyzerOptions.GetStyleCopSettings(); var fileHeader = FileHeaderHelpers.ParseFileHeader(root); - var newSyntaxRoot = fileHeader.IsMissing ? AddHeader(root, document.Name, settings) : ReplaceHeader(document, root, settings); + var newSyntaxRoot = fileHeader.IsMissing ? AddHeader(document, root, document.Name, settings) : ReplaceHeader(document, root, settings); return document.WithSyntaxRoot(newSyntaxRoot); } @@ -104,36 +106,40 @@ private static SyntaxNode ReplaceHeader(Document document, SyntaxNode root, Styl } } - return root.WithLeadingTrivia(CreateNewHeader(document.Name, settings).Add(SyntaxFactory.CarriageReturnLineFeed).Add(SyntaxFactory.CarriageReturnLineFeed).AddRange(trivia)); + string newLineText = document.Project.Solution.Workspace.Options.GetOption(FormattingOptions.NewLine, LanguageNames.CSharp); + return root.WithLeadingTrivia(CreateNewHeader(document.Name, settings, newLineText).Add(SyntaxFactory.CarriageReturnLineFeed).Add(SyntaxFactory.CarriageReturnLineFeed).AddRange(trivia)); } - private static SyntaxNode AddHeader(SyntaxNode root, string name, StyleCopSettings settings) + private static SyntaxNode AddHeader(Document document, SyntaxNode root, string name, StyleCopSettings settings) { - var newTrivia = CreateNewHeader(name, settings).Add(SyntaxFactory.CarriageReturnLineFeed).Add(SyntaxFactory.CarriageReturnLineFeed); + string newLineText = document.Project.Solution.Workspace.Options.GetOption(FormattingOptions.NewLine, LanguageNames.CSharp); + var newTrivia = CreateNewHeader(name, settings, newLineText).Add(SyntaxFactory.CarriageReturnLineFeed).Add(SyntaxFactory.CarriageReturnLineFeed); newTrivia = newTrivia.AddRange(root.GetLeadingTrivia()); return root.WithLeadingTrivia(newTrivia); } - private static SyntaxTriviaList CreateNewHeader(string filename, StyleCopSettings settings) + private static SyntaxTriviaList CreateNewHeader(string filename, StyleCopSettings settings, string newLineText) { - var copyrightText = "// " + GetCopyrightText(settings.DocumentationRules.CopyrightText); + var copyrightText = "// " + GetCopyrightText(settings.DocumentationRules.CopyrightText, newLineText); var newHeader = settings.DocumentationRules.XmlHeader - ? WrapInXmlComment(copyrightText, filename, settings) + ? WrapInXmlComment(copyrightText, filename, settings, newLineText) : copyrightText; return SyntaxFactory.ParseLeadingTrivia(newHeader); } - private static string WrapInXmlComment(string copyrightText, string filename, StyleCopSettings settings) + private static string WrapInXmlComment(string copyrightText, string filename, StyleCopSettings settings, string newLineText) { - return $@"// -{copyrightText} -// "; + return + $"// " + newLineText + + copyrightText + newLineText + + "// "; } - private static string GetCopyrightText(string copyrightText) + private static string GetCopyrightText(string copyrightText, string newLineText) { - return string.Join("\n// ", copyrightText.Split('\n')); + copyrightText = copyrightText.Replace("\r\n", "\n"); + return string.Join(newLineText + "// ", copyrightText.Split('\n')); } } } \ No newline at end of file