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.