From 015a43e543f5b57e31e93156fc98186f84fb03b4 Mon Sep 17 00:00:00 2001 From: Joey Robichaud Date: Fri, 24 May 2019 12:50:36 -0700 Subject: [PATCH] Add end_of_line formatter --- src/CodeFormatter.cs | 1 + src/Formatters/DocumentFormatter.cs | 3 +- src/Formatters/EndOfLineFormatter.cs | 87 +++++++++++++++++++ src/Formatters/FinalNewlineFormatter.cs | 25 ++---- src/Formatters/WhitespaceFormatter.cs | 10 +-- src/Resources.resx | 3 + src/xlf/Resources.cs.xlf | 5 ++ src/xlf/Resources.de.xlf | 5 ++ src/xlf/Resources.es.xlf | 5 ++ src/xlf/Resources.fr.xlf | 5 ++ src/xlf/Resources.it.xlf | 5 ++ src/xlf/Resources.ja.xlf | 5 ++ src/xlf/Resources.ko.xlf | 5 ++ src/xlf/Resources.pl.xlf | 5 ++ src/xlf/Resources.pt-BR.xlf | 5 ++ src/xlf/Resources.ru.xlf | 5 ++ src/xlf/Resources.tr.xlf | 5 ++ src/xlf/Resources.zh-Hans.xlf | 5 ++ src/xlf/Resources.zh-Hant.xlf | 5 ++ tests/Formatters/AbstractFormatterTests.cs | 4 +- tests/Formatters/EndOfLineFormatterTests.cs | 77 ++++++++++++++++ .../Formatters/FinalNewlineFormatterTests.cs | 22 ++--- .../formatted_project/.editorconfig | 2 +- .../unformatted_project/.editorconfig | 2 +- 24 files changed, 260 insertions(+), 41 deletions(-) create mode 100644 src/Formatters/EndOfLineFormatter.cs create mode 100644 tests/Formatters/EndOfLineFormatterTests.cs diff --git a/src/CodeFormatter.cs b/src/CodeFormatter.cs index 6129edefa5..1e89ee22a9 100644 --- a/src/CodeFormatter.cs +++ b/src/CodeFormatter.cs @@ -23,6 +23,7 @@ internal static class CodeFormatter { new WhitespaceFormatter(), new FinalNewlineFormatter(), + new EndOfLineFormatter(), }.ToImmutableArray(); public static async Task FormatWorkspaceAsync( diff --git a/src/Formatters/DocumentFormatter.cs b/src/Formatters/DocumentFormatter.cs index fa9f06f487..98cc3c81c8 100644 --- a/src/Formatters/DocumentFormatter.cs +++ b/src/Formatters/DocumentFormatter.cs @@ -37,6 +37,7 @@ public async Task FormatAsync( /// protected abstract Task FormatFileAsync( Document document, + SourceText sourceText, OptionSet options, ICodingConventionsSnapshot codingConventions, FormatOptions formatOptions, @@ -78,7 +79,7 @@ protected abstract Task FormatFileAsync( logger.LogTrace(Resources.Formatting_code_file_0, Path.GetFileName(document.FilePath)); var originalSourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - var formattedSourceText = await FormatFileAsync(document, options, codingConventions, formatOptions, logger, cancellationToken).ConfigureAwait(false); + var formattedSourceText = await FormatFileAsync(document, originalSourceText, options, codingConventions, formatOptions, logger, cancellationToken).ConfigureAwait(false); return !formattedSourceText.ContentEquals(originalSourceText) ? (originalSourceText, formattedSourceText) diff --git a/src/Formatters/EndOfLineFormatter.cs b/src/Formatters/EndOfLineFormatter.cs new file mode 100644 index 0000000000..cbc87737db --- /dev/null +++ b/src/Formatters/EndOfLineFormatter.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Text; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.CodingConventions; + +namespace Microsoft.CodeAnalysis.Tools.Formatters +{ + internal sealed class EndOfLineFormatter : DocumentFormatter + { + protected override string FormatWarningDescription => Resources.Fix_end_of_line_marker; + + protected override Task FormatFileAsync( + Document document, + SourceText sourceText, + OptionSet options, + ICodingConventionsSnapshot codingConventions, + FormatOptions formatOptions, + ILogger logger, + CancellationToken cancellationToken) + { + return Task.Run(() => + { + if (!TryGetEndOfLine(codingConventions, out var endOfLine)) + { + return sourceText; + } + + var newSourceText = sourceText; + for (var lineIndex = 0; lineIndex < newSourceText.Lines.Count; lineIndex++) + { + var line = newSourceText.Lines[lineIndex]; + var lineEndingSpan = new TextSpan(line.End, line.EndIncludingLineBreak - line.End); + + // Check for end of file + if (lineEndingSpan.Length == 0) + { + break; + } + + var lineEnding = newSourceText.ToString(lineEndingSpan); + + if (lineEnding == endOfLine) + { + continue; + } + + var newLineChange = new TextChange(lineEndingSpan, endOfLine); + newSourceText = newSourceText.WithChanges(newLineChange); + } + + return newSourceText; + }); + } + + public static bool TryGetEndOfLine(ICodingConventionsSnapshot codingConventions, out string endOfLine) + { + if (codingConventions.TryGetConventionValue("end_of_line", out string endOfLineOption)) + { + endOfLine = GetEndOfLine(endOfLineOption); + return true; + } + + endOfLine = null; + return false; + } + + private static string GetEndOfLine(string endOfLineOption) + { + switch (endOfLineOption) + { + case "lf": + return "\n"; + case "cr": + return "\r"; + case "crlf": + return "\r\n"; + default: + return Environment.NewLine; + } + } + } +} diff --git a/src/Formatters/FinalNewlineFormatter.cs b/src/Formatters/FinalNewlineFormatter.cs index 0f1b987bc2..f5c8975ab2 100644 --- a/src/Formatters/FinalNewlineFormatter.cs +++ b/src/Formatters/FinalNewlineFormatter.cs @@ -17,6 +17,7 @@ internal sealed class FinalNewlineFormatter : DocumentFormatter protected override async Task FormatFileAsync( Document document, + SourceText sourceText, OptionSet options, ICodingConventionsSnapshot codingConventions, FormatOptions formatOptions, @@ -28,13 +29,12 @@ protected override async Task FormatFileAsync( return await document.GetTextAsync(cancellationToken); } - var endOfLine = codingConventions.TryGetConventionValue("end_of_line", out string endOfLineOption) - ? GetEndOfLine(endOfLineOption) - : Environment.NewLine; + if (!EndOfLineFormatter.TryGetEndOfLine(codingConventions, out var endOfLine)) + { + endOfLine = Environment.NewLine; + } - var sourceText = await document.GetTextAsync(cancellationToken); var lastLine = sourceText.Lines.Last(); - var hasFinalNewline = lastLine.Span.IsEmpty; if (insertFinalNewline && !hasFinalNewline) @@ -60,20 +60,5 @@ protected override async Task FormatFileAsync( return sourceText; } - - private string GetEndOfLine(string endOfLineOption) - { - switch (endOfLineOption) - { - case "lf": - return "\n"; - case "cr": - return "\r"; - case "crlf": - return "\r\n"; - default: - return Environment.NewLine; - } - } } } diff --git a/src/Formatters/WhitespaceFormatter.cs b/src/Formatters/WhitespaceFormatter.cs index a609df3bc5..2c73ba1ad8 100644 --- a/src/Formatters/WhitespaceFormatter.cs +++ b/src/Formatters/WhitespaceFormatter.cs @@ -21,6 +21,7 @@ internal sealed class WhitespaceFormatter : DocumentFormatter protected override async Task FormatFileAsync( Document document, + SourceText sourceText, OptionSet options, ICodingConventionsSnapshot codingConventions, FormatOptions formatOptions, @@ -33,7 +34,7 @@ protected override async Task FormatFileAsync( } else { - return await GetFormattedDocumentWithDetailedChanges(document, options, cancellationToken); + return await GetFormattedDocumentWithDetailedChanges(document, sourceText, options, cancellationToken); } } @@ -49,13 +50,12 @@ private static async Task GetFormattedDocument(Document document, Op /// /// Returns a formatted with multiple s for each formatting change. /// - private static async Task GetFormattedDocumentWithDetailedChanges(Document document, OptionSet options, CancellationToken cancellationToken) + private static async Task GetFormattedDocumentWithDetailedChanges(Document document, SourceText sourceText, OptionSet options, CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken); - var originalText = await document.GetTextAsync(cancellationToken); - var formattingTextChanges = Formatter.GetFormattedTextChanges(root, document.Project.Solution.Workspace, options, cancellationToken); - return originalText.WithChanges(formattingTextChanges); + + return sourceText.WithChanges(formattingTextChanges); } } } diff --git a/src/Resources.resx b/src/Resources.resx index 0b58b9ae71..33ae19f4cf 100644 --- a/src/Resources.resx +++ b/src/Resources.resx @@ -195,6 +195,9 @@ Add final newline. + + Fix end of line marker. + Fix whitespace formatting. diff --git a/src/xlf/Resources.cs.xlf b/src/xlf/Resources.cs.xlf index 7cc896b5c2..c7b24acc92 100644 --- a/src/xlf/Resources.cs.xlf +++ b/src/xlf/Resources.cs.xlf @@ -42,6 +42,11 @@ Nepodařilo se uložit změny formátování. + + Fix end of line marker. + Fix end of line marker. + + Fix whitespace formatting. Fix whitespace formatting. diff --git a/src/xlf/Resources.de.xlf b/src/xlf/Resources.de.xlf index 01b976ec4d..c3bf63a593 100644 --- a/src/xlf/Resources.de.xlf +++ b/src/xlf/Resources.de.xlf @@ -42,6 +42,11 @@ Fehler beim Speichern von Formatierungsänderungen. + + Fix end of line marker. + Fix end of line marker. + + Fix whitespace formatting. Fix whitespace formatting. diff --git a/src/xlf/Resources.es.xlf b/src/xlf/Resources.es.xlf index ee0a6b02cc..165d8bd43c 100644 --- a/src/xlf/Resources.es.xlf +++ b/src/xlf/Resources.es.xlf @@ -42,6 +42,11 @@ Error al guardar cambios de formato. + + Fix end of line marker. + Fix end of line marker. + + Fix whitespace formatting. Fix whitespace formatting. diff --git a/src/xlf/Resources.fr.xlf b/src/xlf/Resources.fr.xlf index f7af7fa185..a3934f8bd7 100644 --- a/src/xlf/Resources.fr.xlf +++ b/src/xlf/Resources.fr.xlf @@ -42,6 +42,11 @@ L'enregistrement des changements de mise en forme a échoué. + + Fix end of line marker. + Fix end of line marker. + + Fix whitespace formatting. Fix whitespace formatting. diff --git a/src/xlf/Resources.it.xlf b/src/xlf/Resources.it.xlf index f2a1a3a0b1..0c7d782f97 100644 --- a/src/xlf/Resources.it.xlf +++ b/src/xlf/Resources.it.xlf @@ -42,6 +42,11 @@ Non è stato possibile salvare le modifiche di formattazione. + + Fix end of line marker. + Fix end of line marker. + + Fix whitespace formatting. Fix whitespace formatting. diff --git a/src/xlf/Resources.ja.xlf b/src/xlf/Resources.ja.xlf index 21a3fd9c33..9d6ab61da4 100644 --- a/src/xlf/Resources.ja.xlf +++ b/src/xlf/Resources.ja.xlf @@ -42,6 +42,11 @@ 書式変更を保存できませんでした。 + + Fix end of line marker. + Fix end of line marker. + + Fix whitespace formatting. Fix whitespace formatting. diff --git a/src/xlf/Resources.ko.xlf b/src/xlf/Resources.ko.xlf index fb41a80578..9b900c5da9 100644 --- a/src/xlf/Resources.ko.xlf +++ b/src/xlf/Resources.ko.xlf @@ -42,6 +42,11 @@ 서식 변경 내용을 저장하지 못했습니다. + + Fix end of line marker. + Fix end of line marker. + + Fix whitespace formatting. Fix whitespace formatting. diff --git a/src/xlf/Resources.pl.xlf b/src/xlf/Resources.pl.xlf index f1a4eaa976..f206586bc2 100644 --- a/src/xlf/Resources.pl.xlf +++ b/src/xlf/Resources.pl.xlf @@ -42,6 +42,11 @@ Nie można zapisać zmian formatowania. + + Fix end of line marker. + Fix end of line marker. + + Fix whitespace formatting. Fix whitespace formatting. diff --git a/src/xlf/Resources.pt-BR.xlf b/src/xlf/Resources.pt-BR.xlf index c36b0a5f59..2134767bc3 100644 --- a/src/xlf/Resources.pt-BR.xlf +++ b/src/xlf/Resources.pt-BR.xlf @@ -42,6 +42,11 @@ Falha ao salvar alterações de formatação. + + Fix end of line marker. + Fix end of line marker. + + Fix whitespace formatting. Fix whitespace formatting. diff --git a/src/xlf/Resources.ru.xlf b/src/xlf/Resources.ru.xlf index b3260d0075..0f178f1850 100644 --- a/src/xlf/Resources.ru.xlf +++ b/src/xlf/Resources.ru.xlf @@ -42,6 +42,11 @@ Не удалось сохранить изменения форматирования. + + Fix end of line marker. + Fix end of line marker. + + Fix whitespace formatting. Fix whitespace formatting. diff --git a/src/xlf/Resources.tr.xlf b/src/xlf/Resources.tr.xlf index 72aea9a5bc..e25bd5fa63 100644 --- a/src/xlf/Resources.tr.xlf +++ b/src/xlf/Resources.tr.xlf @@ -42,6 +42,11 @@ Biçimlendirme değişiklikleri kaydedilemedi. + + Fix end of line marker. + Fix end of line marker. + + Fix whitespace formatting. Fix whitespace formatting. diff --git a/src/xlf/Resources.zh-Hans.xlf b/src/xlf/Resources.zh-Hans.xlf index cbaaa8981e..14d975c76a 100644 --- a/src/xlf/Resources.zh-Hans.xlf +++ b/src/xlf/Resources.zh-Hans.xlf @@ -42,6 +42,11 @@ 未能保存格式更改。 + + Fix end of line marker. + Fix end of line marker. + + Fix whitespace formatting. Fix whitespace formatting. diff --git a/src/xlf/Resources.zh-Hant.xlf b/src/xlf/Resources.zh-Hant.xlf index c5a18dd184..a33d2fd8b4 100644 --- a/src/xlf/Resources.zh-Hant.xlf +++ b/src/xlf/Resources.zh-Hant.xlf @@ -42,6 +42,11 @@ 無法儲存格式化變更。 + + Fix end of line marker. + Fix end of line marker. + + Fix whitespace formatting. Fix whitespace formatting. diff --git a/tests/Formatters/AbstractFormatterTests.cs b/tests/Formatters/AbstractFormatterTests.cs index 3c8de67a64..7274d4f14d 100644 --- a/tests/Formatters/AbstractFormatterTests.cs +++ b/tests/Formatters/AbstractFormatterTests.cs @@ -82,7 +82,7 @@ private string TestCode public SolutionState TestState { get; } - private protected async Task TestAsync(string testCode, string expectedCode, ICodeFormatter formatter, IReadOnlyDictionary editorConfig) + private protected async Task TestAsync(string testCode, string expectedCode, IReadOnlyDictionary editorConfig) { TestCode = testCode; @@ -103,7 +103,7 @@ private protected async Task TestAsync(string testCode, string expectedCode, ICo var filesToFormat = new[] { (document, options, codingConventions) }.ToImmutableArray(); - var formattedSolution = await formatter.FormatAsync(solution, filesToFormat, formatOptions, Logger, default); + var formattedSolution = await Formatter.FormatAsync(solution, filesToFormat, formatOptions, Logger, default); var formattedDocument = formattedSolution.Projects.Single().Documents.Single(); var formattedText = await formattedDocument.GetTextAsync(); diff --git a/tests/Formatters/EndOfLineFormatterTests.cs b/tests/Formatters/EndOfLineFormatterTests.cs new file mode 100644 index 0000000000..33439abbe6 --- /dev/null +++ b/tests/Formatters/EndOfLineFormatterTests.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Tools.Formatters; +using Xunit; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Formatters +{ + public class EndOfLineFormatterTests : CSharpFormatterTests + { + private protected override ICodeFormatter Formatter => new EndOfLineFormatter(); + + [Theory] + [InlineData("\n", "\n", "lf")] + [InlineData("\r\n", "\n", "lf")] + [InlineData("\r", "\n", "lf")] + [InlineData("\n", "\r\n", "crlf")] + [InlineData("\r\n", "\r\n", "crlf")] + [InlineData("\r", "\r\n", "crlf")] + [InlineData("\n", "\r", "cr")] + [InlineData("\r\n", "\r", "cr")] + [InlineData("\r", "\r", "cr")] + public async Task TestEndOfLine_NoFinalNewline(string codeNewline, string expectedNewline, string endOfLine) + { + var testCode = $"class C{codeNewline}{{{codeNewline}}}"; + + var expectedCode = $"class C{expectedNewline}{{{expectedNewline}}}"; + + var editorConfig = new Dictionary() + { + ["end_of_line"] = endOfLine, + }; + + await TestAsync(testCode, expectedCode, editorConfig); + } + + [Theory] + [InlineData("\n", "\n", "lf")] + [InlineData("\r\n", "\n", "lf")] + [InlineData("\r", "\n", "lf")] + [InlineData("\n", "\r\n", "crlf")] + [InlineData("\r\n", "\r\n", "crlf")] + [InlineData("\r", "\r\n", "crlf")] + [InlineData("\n", "\r", "cr")] + [InlineData("\r\n", "\r", "cr")] + [InlineData("\r", "\r", "cr")] + public async Task TestEndOfLine_WithFinalNewline(string codeNewline, string expectedNewline, string endOfLine) + { + var testCode = $"class C{codeNewline}{{{codeNewline}}}{codeNewline}"; + + var expectedCode = $"class C{expectedNewline}{{{expectedNewline}}}{expectedNewline}"; + + var editorConfig = new Dictionary() + { + ["end_of_line"] = endOfLine, + }; + + await TestAsync(testCode, expectedCode, editorConfig); + } + + [Theory] + [InlineData("\n")] + [InlineData("\r\n")] + [InlineData("\r")] + public async Task TestEndOfLine_AndNoSetting_NoChanges(string codeNewline) + { + var testCode = $"class C{codeNewline}{{{codeNewline}}}{codeNewline}"; + + var editorConfig = new Dictionary() + { + }; + + await TestAsync(testCode, testCode, editorConfig); + } + } +} diff --git a/tests/Formatters/FinalNewlineFormatterTests.cs b/tests/Formatters/FinalNewlineFormatterTests.cs index 105734e7e8..019b859d3f 100644 --- a/tests/Formatters/FinalNewlineFormatterTests.cs +++ b/tests/Formatters/FinalNewlineFormatterTests.cs @@ -29,7 +29,7 @@ class C ["end_of_line"] = "crlf", }; - await TestAsync(testCode, expectedCode, Formatter, editorConfig); + await TestAsync(testCode, expectedCode, editorConfig); } [Fact] @@ -52,7 +52,7 @@ class C ["end_of_line"] = "crlf", }; - await TestAsync(testCode, expectedCode, Formatter, editorConfig); + await TestAsync(testCode, expectedCode, editorConfig); } [Fact] @@ -68,7 +68,7 @@ public async Task WhenFinalNewlineRequired_AndEndOfLineIsLineFeed_LineFeedAdded( ["end_of_line"] = "lf", }; - await TestAsync(testCode, expectedCode, Formatter, editorConfig); + await TestAsync(testCode, expectedCode, editorConfig); } [Fact] @@ -84,7 +84,7 @@ public async Task WhenFinalNewlineRequired_AndEndOfLineIsCarriageReturnLineFeed_ ["end_of_line"] = "crlf", }; - await TestAsync(testCode, expectedCode, Formatter, editorConfig); + await TestAsync(testCode, expectedCode, editorConfig); } [Fact] @@ -100,7 +100,7 @@ public async Task WhenFinalNewlineRequired_AndEndOfLineIsCarriageReturn_Carriage ["end_of_line"] = "cr", }; - await TestAsync(testCode, expectedCode, Formatter, editorConfig); + await TestAsync(testCode, expectedCode, editorConfig); } [Fact] public async Task WhenFinalNewlineRequired_AndFinalNewlineExits_NoChange() @@ -123,7 +123,7 @@ class C ["end_of_line"] = "crlf", }; - await TestAsync(testCode, expectedCode, Formatter, editorConfig); + await TestAsync(testCode, expectedCode, editorConfig); } [Fact] @@ -139,7 +139,7 @@ public async Task WhenFinalNewlineUnwanted_AndFinalNewlineExists_CarriageReturnL ["end_of_line"] = "crlf", }; - await TestAsync(testCode, expectedCode, Formatter, editorConfig); + await TestAsync(testCode, expectedCode, editorConfig); } [Fact] @@ -155,7 +155,7 @@ public async Task WhenFinalNewlineUnwanted_AndFinalNewlineExists_LineFeedRemoved ["end_of_line"] = "crlf", }; - await TestAsync(testCode, expectedCode, Formatter, editorConfig); + await TestAsync(testCode, expectedCode, editorConfig); } [Fact] @@ -171,7 +171,7 @@ public async Task WhenFinalNewlineUnwanted_AndFinalNewlineExists_CarriageReturnR ["end_of_line"] = "crlf", }; - await TestAsync(testCode, expectedCode, Formatter, editorConfig); + await TestAsync(testCode, expectedCode, editorConfig); } [Fact] @@ -193,7 +193,7 @@ class C ["end_of_line"] = "crlf", }; - await TestAsync(testCode, expectedCode, Formatter, editorConfig); + await TestAsync(testCode, expectedCode, editorConfig); } [Fact] @@ -209,7 +209,7 @@ public async Task WhenFinalNewlineUnwanted_AndFileIsEmpty_NoChange() ["end_of_line"] = "crlf", }; - await TestAsync(testCode, expectedCode, Formatter, editorConfig); + await TestAsync(testCode, expectedCode, editorConfig); } } } diff --git a/tests/projects/for_code_formatter/formatted_project/.editorconfig b/tests/projects/for_code_formatter/formatted_project/.editorconfig index 22ee707db7..5ba03353df 100644 --- a/tests/projects/for_code_formatter/formatted_project/.editorconfig +++ b/tests/projects/for_code_formatter/formatted_project/.editorconfig @@ -12,7 +12,7 @@ indent_style = space tab_width = 4 # New line preferences -end_of_line = crlf +# end_of_line = crlf insert_final_newline = false #### .NET Coding Conventions #### diff --git a/tests/projects/for_code_formatter/unformatted_project/.editorconfig b/tests/projects/for_code_formatter/unformatted_project/.editorconfig index 11c856d16f..8b5bebcf12 100644 --- a/tests/projects/for_code_formatter/unformatted_project/.editorconfig +++ b/tests/projects/for_code_formatter/unformatted_project/.editorconfig @@ -12,7 +12,7 @@ indent_style = space tab_width = 2 # New line preferences -end_of_line = crlf +# end_of_line = crlf insert_final_newline = false #### .NET Coding Conventions ####