diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1518UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1518UnitTests.cs index 828070f66..539607f8a 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1518UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1518UnitTests.cs @@ -29,6 +29,8 @@ public void Bar(int i) } }"; + private const string WhiteSpace = "\t "; + /// /// Verifies that blank lines at the end of the file will produce a warning. /// @@ -49,6 +51,76 @@ internal async Task TestWithBlankLinesAtEndOfFileAsync(OptionSetting? newlineAtE await VerifyCSharpFixAsync(newlineAtEndOfFile, testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); } + /// + /// Verifies file with white space only and no cr/lf at end of file will produce a warning when setting requires. + /// + /// The effective setting. + /// The expected text to appear at the end of the file. + /// A representing the asynchronous unit test. + [Theory] + [InlineData(null, null)] + [InlineData(OptionSetting.Allow, null)] + [InlineData(OptionSetting.Require, "\r\n")] + [InlineData(OptionSetting.Omit, null)] + internal async Task TestWithWhiteSpaceOnlyAsync(OptionSetting? newlineAtEndOfFile, string expectedText) + { + var testCode = WhiteSpace; + var fixedCode = expectedText; + + if (expectedText == null) + { + await VerifyCSharpDiagnosticAsync(newlineAtEndOfFile, testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + else + { + var expected = Diagnostic(this.GetDescriptor(newlineAtEndOfFile)).WithLocation(1, 1); + await VerifyCSharpFixAsync(newlineAtEndOfFile, testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); + } + } + + /// + /// Verifies file with white space only and cr/lf at end of file will produce a warning when setting requires. + /// + /// The effective setting. + /// The expected text to appear at the end of the file. + /// A representing the asynchronous unit test. + [Theory] + [InlineData(null, null)] + [InlineData(OptionSetting.Allow, null)] + [InlineData(OptionSetting.Require, null)] + [InlineData(OptionSetting.Omit, "")] + internal async Task TestWithWhiteSpaceAndNewlineOnlyAsync(OptionSetting? newlineAtEndOfFile, string expectedText) + { + var testCode = WhiteSpace + "\r\n"; + var fixedCode = expectedText; + + if (expectedText == null) + { + await VerifyCSharpDiagnosticAsync(newlineAtEndOfFile, testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + else + { + var expected = Diagnostic(this.GetDescriptor(newlineAtEndOfFile)).WithLocation(1, 1); + await VerifyCSharpFixAsync(newlineAtEndOfFile, testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); + } + } + + /// + /// Verifies that empty files will not produce a warning. + /// + /// The effective setting. + /// A representing the asynchronous unit test. + [Theory] + [InlineData(null)] + [InlineData(OptionSetting.Allow)] + [InlineData(OptionSetting.Require)] + [InlineData(OptionSetting.Omit)] + internal async Task TestWithEmptyFileAsync(OptionSetting? newlineAtEndOfFile) + { + var testCode = string.Empty; + await VerifyCSharpDiagnosticAsync(newlineAtEndOfFile, testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + /// /// Verifies that linefeed only blank lines at the end of the file will produce a warning. /// diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs index c917df7a5..d9a41dac4 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs @@ -72,6 +72,14 @@ public static bool IsWhitespaceOnly(this SyntaxTree tree, CancellationToken canc && TriviaHelper.IndexOfFirstNonWhitespaceTrivia(firstToken.LeadingTrivia) == -1; } + public static bool IsEmpty(this SyntaxTree tree, CancellationToken cancellationToken) + { + var root = tree.GetRoot(cancellationToken); + var firstToken = root.GetFirstToken(includeZeroWidth: true); + + return firstToken.IsKind(SyntaxKind.EndOfFileToken) && firstToken.FullSpan.IsEmpty; + } + internal static bool ContainsUsingAlias(this SyntaxTree tree, ConcurrentDictionary cache) { if (tree == null) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1518UseLineEndingsCorrectlyAtEndOfFile.cs b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1518UseLineEndingsCorrectlyAtEndOfFile.cs index 90e697efa..7b306c8de 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1518UseLineEndingsCorrectlyAtEndOfFile.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1518UseLineEndingsCorrectlyAtEndOfFile.cs @@ -185,6 +185,12 @@ private static void HandleSyntaxTree(SyntaxTreeAnalysisContext context, StyleCop break; } + if (context.Tree.IsEmpty(context.CancellationToken)) + { + // Empty files never contain line endings. + return; + } + context.ReportDiagnostic(Diagnostic.Create(descriptorToReport, Location.Create(context.Tree, reportedSpan))); } }