From 5b001954a15ccfb4b2c44982c9a737c7395bd599 Mon Sep 17 00:00:00 2001 From: Daniel Plaisted Date: Fri, 29 May 2015 14:56:48 -0700 Subject: [PATCH 1/4] Add code formatting rule to make sure there are newlines at the end of code files --- .../Microsoft.DotNet.CodeFormatting.csproj | 1 + .../Rules/NewLineAtEndOfFileRule.cs | 44 +++++++++++++++++++ .../Rules/RuleOrder.cs | 1 + .../SyntaxUtil.cs | 17 +++++++ 4 files changed, 63 insertions(+) create mode 100644 src/Microsoft.DotNet.CodeFormatting/Rules/NewLineAtEndOfFileRule.cs diff --git a/src/Microsoft.DotNet.CodeFormatting/Microsoft.DotNet.CodeFormatting.csproj b/src/Microsoft.DotNet.CodeFormatting/Microsoft.DotNet.CodeFormatting.csproj index e549e17d..c88f3f17 100644 --- a/src/Microsoft.DotNet.CodeFormatting/Microsoft.DotNet.CodeFormatting.csproj +++ b/src/Microsoft.DotNet.CodeFormatting/Microsoft.DotNet.CodeFormatting.csproj @@ -88,6 +88,7 @@ + diff --git a/src/Microsoft.DotNet.CodeFormatting/Rules/NewLineAtEndOfFileRule.cs b/src/Microsoft.DotNet.CodeFormatting/Rules/NewLineAtEndOfFileRule.cs new file mode 100644 index 00000000..dae28d20 --- /dev/null +++ b/src/Microsoft.DotNet.CodeFormatting/Rules/NewLineAtEndOfFileRule.cs @@ -0,0 +1,44 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.DotNet.CodeFormatting.Rules +{ + [SyntaxRule(SyntaxRuleOrder.NewLineAtEndOfFileRule)] + internal sealed class NewLineAtEndOfFileRule : CSharpOnlyFormattingRule, ISyntaxFormattingRule + { + public SyntaxNode Process(SyntaxNode syntaxRoot, string languageName) + { + bool needsNewLine; + var lastToken = syntaxRoot.GetLastToken(); + if (!lastToken.HasTrailingTrivia) + { + needsNewLine = true; + } + else + { + var lastTrivia = lastToken.TrailingTrivia.Last(); + if (lastTrivia.IsKind(SyntaxKind.EndOfLineTrivia)) + { + needsNewLine = false; + } + else + { + needsNewLine = true; + } + } + + if (needsNewLine) + { + var newLine = SyntaxUtil.GetBestNewLineTriviaRecursive(lastToken.Parent); + var newLastToken = lastToken.WithTrailingTrivia(lastToken.TrailingTrivia.Concat(new[] { newLine })); + return syntaxRoot.ReplaceToken(lastToken, newLastToken); + } + return syntaxRoot; + } + } +} diff --git a/src/Microsoft.DotNet.CodeFormatting/Rules/RuleOrder.cs b/src/Microsoft.DotNet.CodeFormatting/Rules/RuleOrder.cs index 7ce282ae..2bccad21 100644 --- a/src/Microsoft.DotNet.CodeFormatting/Rules/RuleOrder.cs +++ b/src/Microsoft.DotNet.CodeFormatting/Rules/RuleOrder.cs @@ -16,6 +16,7 @@ internal static class SyntaxRuleOrder public const int CopyrightHeaderRule = 2; public const int UsingLocationFormattingRule = 3; public const int NewLineAboveFormattingRule = 4; + public const int NewLineAtEndOfFileRule = 5; public const int BraceNewLineRule = 6; public const int NonAsciiChractersAreEscapedInLiterals = 7; } diff --git a/src/Microsoft.DotNet.CodeFormatting/SyntaxUtil.cs b/src/Microsoft.DotNet.CodeFormatting/SyntaxUtil.cs index b1a61e56..eed94474 100644 --- a/src/Microsoft.DotNet.CodeFormatting/SyntaxUtil.cs +++ b/src/Microsoft.DotNet.CodeFormatting/SyntaxUtil.cs @@ -30,6 +30,23 @@ internal static SyntaxTrivia GetBestNewLineTrivia(SyntaxNode node, SyntaxTrivia? return defaultNewLineTrivia ?? SyntaxFactory.CarriageReturnLineFeed; } + internal static SyntaxTrivia GetBestNewLineTriviaRecursive(SyntaxNode node, SyntaxTrivia? defaultNewLineTrivia = null) + { + while(node != null) + { + SyntaxTrivia trivia; + if (TryGetExistingNewLine(node.GetLeadingTrivia(), out trivia) || + TryGetExistingNewLine(node.GetTrailingTrivia(), out trivia)) + { + return trivia; + } + + node = node.Parent; + } + + return defaultNewLineTrivia ?? SyntaxFactory.CarriageReturnLineFeed; + } + internal static SyntaxTrivia GetBestNewLineTrivia(SyntaxToken token, SyntaxTrivia? defaultNewLineTrivia = null) { SyntaxTrivia trivia; From baf7e1a8b72c5fc2374b83f1593f8dcf38c92ee2 Mon Sep 17 00:00:00 2001 From: Daniel Plaisted Date: Mon, 1 Jun 2015 19:05:01 -0700 Subject: [PATCH 2/4] Update tests to match NewLineAtEndOfFileRule --- .../Rules/CombinationTest.cs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.DotNet.CodeFormatting.Tests/Rules/CombinationTest.cs b/src/Microsoft.DotNet.CodeFormatting.Tests/Rules/CombinationTest.cs index 9aa4f00b..a9648210 100644 --- a/src/Microsoft.DotNet.CodeFormatting.Tests/Rules/CombinationTest.cs +++ b/src/Microsoft.DotNet.CodeFormatting.Tests/Rules/CombinationTest.cs @@ -69,7 +69,8 @@ private void M() { N(_field); } -}"; +} +"; Verify(text, expected, runFormatter: false); } @@ -96,7 +97,8 @@ private void M() { _field = 42; } -}"; +} +"; Verify(text, expected, runFormatter: false); } @@ -119,7 +121,8 @@ internal class C #if DOG void M() { } #endif -}"; +} +"; Verify(text, expected, runFormatter: false); } @@ -145,7 +148,8 @@ internal void M() { } #endif -}"; +} +"; s_formattingEngine.PreprocessorConfigurations = ImmutableArray.CreateRange(new[] { new[] { "DOG" } }); Verify(text, expected, runFormatter: false); @@ -179,7 +183,8 @@ private void G() void M() { } #endif -}"; +} +"; Verify(text, expected, runFormatter: false); } @@ -219,7 +224,8 @@ void G() void M() { } #endif -}"; +} +"; s_formattingEngine.PreprocessorConfigurations = ImmutableArray.CreateRange(new[] { new[] { "TEST" } }); Verify(text, expected, runFormatter: false); @@ -262,7 +268,8 @@ private void M() { } } -}"; +} +"; Verify(source, expected, runFormatter: false); } From 59ed9d2afbee2e8ebf87b05242b693cfc386c461 Mon Sep 17 00:00:00 2001 From: Daniel Plaisted Date: Mon, 1 Jun 2015 19:31:09 -0700 Subject: [PATCH 3/4] Add tests for NewLineAtEndOfFileRule --- ...crosoft.DotNet.CodeFormatting.Tests.csproj | 1 + .../Rules/NewLineAtEndOfFileRuleTests.cs | 86 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/Microsoft.DotNet.CodeFormatting.Tests/Rules/NewLineAtEndOfFileRuleTests.cs 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 2f26297e..0dec6077 100644 --- a/src/Microsoft.DotNet.CodeFormatting.Tests/Microsoft.DotNet.CodeFormatting.Tests.csproj +++ b/src/Microsoft.DotNet.CodeFormatting.Tests/Microsoft.DotNet.CodeFormatting.Tests.csproj @@ -112,6 +112,7 @@ + diff --git a/src/Microsoft.DotNet.CodeFormatting.Tests/Rules/NewLineAtEndOfFileRuleTests.cs b/src/Microsoft.DotNet.CodeFormatting.Tests/Rules/NewLineAtEndOfFileRuleTests.cs new file mode 100644 index 00000000..47d3a41f --- /dev/null +++ b/src/Microsoft.DotNet.CodeFormatting.Tests/Rules/NewLineAtEndOfFileRuleTests.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.DotNet.CodeFormatting.Tests +{ + public class NewLineAtEndOfFileRuleTests : SyntaxRuleTestBase + { + internal override ISyntaxFormattingRule Rule + { + get { return new Rules.NewLineAtEndOfFileRule(); } + } + + [Fact] + public void SimpleClassWithoutNewLineAtEndOfFile() + { + var text = @"using System; +public class TestClass +{ +}"; + + var expected = @"using System; +public class TestClass +{ +} +"; + + Verify(text, expected); + } + + [Fact] + public void SimpleClassWithNewLineAtEndOfFile() + { + var text = @"using System; +public class TestClass +{ +} +"; + + Verify(text, text); + } + + [Fact] + public void CommentAtEndOfFile() + { + var text = @"using System; +public class TestClass +{ +} +// Hello World"; + + var expected = @"using System; +public class TestClass +{ +} +// Hello World +"; + + Verify(text, expected); + } + + [Fact] + public void IfDefAtEndOfFile() + { + var text = @"using System; +public class TestClass +{ +} +#if TEST +#endif"; + + var expected = @"using System; +public class TestClass +{ +} +#if TEST +#endif +"; + + Verify(text, expected); + } + } +} From 0f6a404e199f0547a171f33cebeb215a3acd5e33 Mon Sep 17 00:00:00 2001 From: Daniel Plaisted Date: Tue, 2 Jun 2015 10:49:53 -0700 Subject: [PATCH 4/4] Add newlines to leading trivia of EndOfFileToken, if necessary --- .../Rules/NewLineAtEndOfFileRule.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/Microsoft.DotNet.CodeFormatting/Rules/NewLineAtEndOfFileRule.cs b/src/Microsoft.DotNet.CodeFormatting/Rules/NewLineAtEndOfFileRule.cs index dae28d20..43cdb33d 100644 --- a/src/Microsoft.DotNet.CodeFormatting/Rules/NewLineAtEndOfFileRule.cs +++ b/src/Microsoft.DotNet.CodeFormatting/Rules/NewLineAtEndOfFileRule.cs @@ -14,6 +14,17 @@ internal sealed class NewLineAtEndOfFileRule : CSharpOnlyFormattingRule, ISyntax public SyntaxNode Process(SyntaxNode syntaxRoot, string languageName) { bool needsNewLine; + var endOfFileToken = syntaxRoot.GetLastToken(true, true, true, true); + if (!endOfFileToken.IsKind(SyntaxKind.EndOfFileToken)) + { + throw new InvalidOperationException("Expected last token to be EndOfFileToken, was actually: " + endOfFileToken.Kind()); + } + + if (endOfFileToken.HasLeadingTrivia) + { + return AddNewLineToEndOfFileTokenLeadingTriviaIfNecessary(syntaxRoot, endOfFileToken); + } + var lastToken = syntaxRoot.GetLastToken(); if (!lastToken.HasTrailingTrivia) { @@ -40,5 +51,20 @@ public SyntaxNode Process(SyntaxNode syntaxRoot, string languageName) } return syntaxRoot; } + + SyntaxNode AddNewLineToEndOfFileTokenLeadingTriviaIfNecessary(SyntaxNode syntaxRoot, SyntaxToken endofFileToken) + { + if (endofFileToken.LeadingTrivia.Last().IsKind(SyntaxKind.EndOfLineTrivia)) + { + return syntaxRoot; + } + + var newLine = SyntaxUtil.GetBestNewLineTriviaRecursive(endofFileToken.Parent); + var newLastToken = endofFileToken.WithTrailingTrivia(endofFileToken.TrailingTrivia.Concat(new[] { newLine })); + return syntaxRoot.ReplaceToken(endofFileToken, newLastToken); + + + return syntaxRoot; + } } }