diff --git a/src/Microsoft.DotNet.CodeFormatting.Tests/Microsoft.DotNet.CodeFormatting.Tests.csproj b/src/Microsoft.DotNet.CodeFormatting.Tests/Microsoft.DotNet.CodeFormatting.Tests.csproj index 5bfd8881..6f6da949 100644 --- a/src/Microsoft.DotNet.CodeFormatting.Tests/Microsoft.DotNet.CodeFormatting.Tests.csproj +++ b/src/Microsoft.DotNet.CodeFormatting.Tests/Microsoft.DotNet.CodeFormatting.Tests.csproj @@ -109,6 +109,7 @@ + diff --git a/src/Microsoft.DotNet.CodeFormatting.Tests/Rules/HasNewLineAtEndOfFileFormattingRuleTests.cs b/src/Microsoft.DotNet.CodeFormatting.Tests/Rules/HasNewLineAtEndOfFileFormattingRuleTests.cs new file mode 100644 index 00000000..c2cc08c5 --- /dev/null +++ b/src/Microsoft.DotNet.CodeFormatting.Tests/Rules/HasNewLineAtEndOfFileFormattingRuleTests.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Xunit; + +namespace Microsoft.DotNet.CodeFormatting.Tests +{ + public class HasNewLineAtEndOfFileFormattingRuleTests : SyntaxRuleTestBase + { + internal override ISyntaxFormattingRule Rule + { + get { return new Rules.HasNewLineAtEndOfFileFormattingRule(); } + } + + [Fact] + public void ShouldAddNewLine() + { + var source = "public class C { }"; + + var expected = "public class C { }\r\n"; + + Verify(source, expected); + } + + [Fact] + public void ShouldRemoveExtraNewLines() + { + var source = "public class C { }\r\n\r\n\n\r\n\n\r"; + + var expected = "public class C { }\r\n"; + + Verify(source, expected); + } + + [Fact] + public void ShouldNotCareAboutExistingCarriageReturn() + { + var source = "public class C { }\r"; + + var expected = "public class C { }\r\n"; + + Verify(source, expected); + } + + [Fact] + public void ShouldNotCareAboutExistingLineFeed() + { + var source = "public class C { }\n"; + + var expected = "public class C { }\r\n"; + + Verify(source, expected); + } + + [Fact] + public void ShouldHandleEmptyDocument() + { + var source = string.Empty; + + var expected = "\r\n"; + + Verify(source, expected); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.CodeFormatting/Microsoft.DotNet.CodeFormatting.csproj b/src/Microsoft.DotNet.CodeFormatting/Microsoft.DotNet.CodeFormatting.csproj index 821576ef..132ff304 100644 --- a/src/Microsoft.DotNet.CodeFormatting/Microsoft.DotNet.CodeFormatting.csproj +++ b/src/Microsoft.DotNet.CodeFormatting/Microsoft.DotNet.CodeFormatting.csproj @@ -89,6 +89,7 @@ + @@ -112,4 +113,4 @@ --> - + \ No newline at end of file diff --git a/src/Microsoft.DotNet.CodeFormatting/Rules/HasNewLineAtEndOfFileFormattingRule.cs b/src/Microsoft.DotNet.CodeFormatting/Rules/HasNewLineAtEndOfFileFormattingRule.cs new file mode 100644 index 00000000..222fb807 --- /dev/null +++ b/src/Microsoft.DotNet.CodeFormatting/Rules/HasNewLineAtEndOfFileFormattingRule.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Microsoft.DotNet.CodeFormatting.Rules +{ + [SyntaxRuleOrder(SyntaxRuleOrder.HasNewLineAtEndOfFile)] + internal sealed class HasNewLineAtEndOfFileFormattingRule : ISyntaxFormattingRule + { + public SyntaxNode Process(SyntaxNode syntaxRoot) + { + var node = syntaxRoot.DescendantNodes().LastOrDefault(); + + if (node != null) + { + syntaxRoot = syntaxRoot.ReplaceNode(node, RemoveNewLines(node)); + } + + var token = syntaxRoot.DescendantTokens().Single(x => x.IsKind(SyntaxKind.EndOfFileToken)); + + return syntaxRoot.ReplaceToken(token, AdjustNewLines(token)); + } + + private static SyntaxNode RemoveNewLines(SyntaxNode node) + { + var newTrivia = Enumerable.Empty(); + + if (node.HasTrailingTrivia) + { + newTrivia = node.GetTrailingTrivia().Where(x => !x.IsKind(SyntaxKind.EndOfLineTrivia)); + } + + return node.WithTrailingTrivia(newTrivia); + } + + private static SyntaxToken AdjustNewLines(SyntaxToken token) + { + var newTrivia = Enumerable.Empty(); + + if (token.HasLeadingTrivia) + { + newTrivia = token.LeadingTrivia.Where(x => !x.IsKind(SyntaxKind.EndOfLineTrivia)); + } + + return token.WithLeadingTrivia(newTrivia.AddNewLine()); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.CodeFormatting/Rules/RuleOrder.cs b/src/Microsoft.DotNet.CodeFormatting/Rules/RuleOrder.cs index a30ce50c..c5cc4fc9 100644 --- a/src/Microsoft.DotNet.CodeFormatting/Rules/RuleOrder.cs +++ b/src/Microsoft.DotNet.CodeFormatting/Rules/RuleOrder.cs @@ -19,6 +19,7 @@ internal static class SyntaxRuleOrder public const int HasNewLineBeforeFirstNamespaceFormattingRule = 5; public const int BraceNewLineRule = 6; public const int NonAsciiChractersAreEscapedInLiterals = 7; + public const int HasNewLineAtEndOfFile = 8; } // Please keep these values sorted by number, not rule name.