diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/SourceTextExtensions.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/SourceTextExtensions.cs index 77b8b9b1b8e..954acc93ae1 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/SourceTextExtensions.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/SourceTextExtensions.cs @@ -296,4 +296,33 @@ public static TextEdit[] MinimizeTextEdits(this SourceText text, TextEdit[] edit var cleanEdits = cleanChanges.Select(text.GetTextEdit).ToArray(); return cleanEdits; } + + /// + /// Determines if the given has more LF line endings ('\n') than CRLF line endings ('\r\n'). + /// + /// The to examine. + /// + /// true if the is deemed to use LF line endings; otherwise, false. + /// + public static bool HasLFLineEndings(this SourceText text) + { + var crlfCount = 0; + var lfCount = 0; + + foreach (var line in text.Lines) + { + var lineBreakSpan = TextSpan.FromBounds(line.End, line.EndIncludingLineBreak); + var lineBreak = line.Text?.ToString(lineBreakSpan) ?? string.Empty; + if (lineBreak == "\r\n") + { + crlfCount++; + } + else if (lineBreak == "\n") + { + lfCount++; + } + } + + return lfCount > crlfCount; + } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpFormattingPassBase.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpFormattingPassBase.cs index 4387ce3d7e6..e785aa49500 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpFormattingPassBase.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/Passes/CSharpFormattingPassBase.cs @@ -133,7 +133,10 @@ protected async Task> AdjustIndentationAsync(FormattingContext } var scopeOwner = syntaxTreeRoot.FindInnermostNode(originalLocation); - sourceMappingIndentations[originalLocation] = new IndentationData(indentation); + if (!sourceMappingIndentations.ContainsKey(originalLocation)) + { + sourceMappingIndentations[originalLocation] = new IndentationData(indentation); + } // For @section blocks we have special handling to add a fake source mapping/significant location at the end of the // section, to return the indentation back to before the start of the section block. diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/RazorFormattingService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/RazorFormattingService.cs index d9f012ff881..176d050550b 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/RazorFormattingService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Formatting/RazorFormattingService.cs @@ -108,7 +108,8 @@ public async Task GetDocumentFormattingEditsAsync( ? result : result.Where(e => range.LineOverlapsWith(e.Range)).ToArray(); - return originalText.MinimizeTextEdits(filteredEdits); + var normalizedEdits = NormalizeLineEndings(originalText, filteredEdits); + return originalText.MinimizeTextEdits(normalizedEdits); } public Task GetCSharpOnTypeFormattingEditsAsync(DocumentContext documentContext, RazorFormattingOptions options, int hostDocumentIndex, char triggerCharacter, CancellationToken cancellationToken) @@ -279,4 +280,22 @@ private static void UnwrapCSharpSnippets(TextEdit[] razorEdits) edit.NewText = edit.NewText.Replace("/*$0*/", "$0"); } } + + /// + /// This method counts the occurrences of CRLF and LF line endings in the original text. + /// If LF line endings are more prevalent, it removes any CR characters from the text edits + /// to ensure consistency with the LF style. + /// + private TextEdit[] NormalizeLineEndings(SourceText originalText, TextEdit[] edits) + { + if (originalText.HasLFLineEndings()) + { + foreach (var edit in edits) + { + edit.NewText = edit.NewText.Replace("\r", ""); + } + } + + return edits; + } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CodeDirectiveFormattingTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CodeDirectiveFormattingTest.cs index 80589eb18ff..24ca9cf007a 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CodeDirectiveFormattingTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/CodeDirectiveFormattingTest.cs @@ -1699,7 +1699,8 @@ await RunFormattingTestAsync( private IEnumerable _items = new[] { 1, 2, 3, 4, 5 }; } """, - tagHelpers: GetComponentWithCascadingTypeParameter()); + tagHelpers: GetComponentWithCascadingTypeParameter(), + skipFlipLineEndingTest: true); } [Fact] @@ -1789,7 +1790,8 @@ await RunFormattingTestAsync( private IEnumerable _items2 = new long[] { 1, 2, 3, 4, 5 }; } """, - tagHelpers: GetComponentWithTwoCascadingTypeParameter()); + tagHelpers: GetComponentWithTwoCascadingTypeParameter(), + skipFlipLineEndingTest: true); // tracked by https://github.com/dotnet/razor/issues/10836 } [Fact] diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs index 8835b479edc..a43adfed09a 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/FormattingTestBase.cs @@ -49,14 +49,26 @@ private protected async Task RunFormattingTestAsync( ImmutableArray tagHelpers = default, bool allowDiagnostics = false, RazorLSPOptions? razorLSPOptions = null, - bool inGlobalNamespace = false) + bool inGlobalNamespace = false, + bool skipFlipLineEndingTest = false) { // Run with and without forceRuntimeCodeGeneration - await RunFormattingTestAsync(input, expected, tabSize, insertSpaces, fileKind, tagHelpers, allowDiagnostics, razorLSPOptions, inGlobalNamespace, forceRuntimeCodeGeneration: true); - await RunFormattingTestAsync(input, expected, tabSize, insertSpaces, fileKind, tagHelpers, allowDiagnostics, razorLSPOptions, inGlobalNamespace, forceRuntimeCodeGeneration: false); + await RunFormattingTestInternalAsync(input, expected, tabSize, insertSpaces, fileKind, tagHelpers, allowDiagnostics, razorLSPOptions, inGlobalNamespace, forceRuntimeCodeGeneration: true); + await RunFormattingTestInternalAsync(input, expected, tabSize, insertSpaces, fileKind, tagHelpers, allowDiagnostics, razorLSPOptions, inGlobalNamespace, forceRuntimeCodeGeneration: false); + + // some tests are failing, skip for now, tracked by https://github.com/dotnet/razor/issues/10836 + if (!skipFlipLineEndingTest) + { + // flip the line endings of the stings (LF to CRLF and vice versa) and run again + input = FlipLineEndings(input); + expected = FlipLineEndings(expected); + + await RunFormattingTestInternalAsync(input, expected, tabSize, insertSpaces, fileKind, tagHelpers, allowDiagnostics, razorLSPOptions, inGlobalNamespace, forceRuntimeCodeGeneration: true); + await RunFormattingTestInternalAsync(input, expected, tabSize, insertSpaces, fileKind, tagHelpers, allowDiagnostics, razorLSPOptions, inGlobalNamespace, forceRuntimeCodeGeneration: false); + } } - private async Task RunFormattingTestAsync(string input, string expected, int tabSize, bool insertSpaces, string? fileKind, ImmutableArray tagHelpers, bool allowDiagnostics, RazorLSPOptions? razorLSPOptions, bool inGlobalNamespace, bool forceRuntimeCodeGeneration) + private async Task RunFormattingTestInternalAsync(string input, string expected, int tabSize, bool insertSpaces, string? fileKind, ImmutableArray tagHelpers, bool allowDiagnostics, RazorLSPOptions? razorLSPOptions, bool inGlobalNamespace, bool forceRuntimeCodeGeneration) { // Arrange fileKind ??= FileKinds.Component; @@ -351,4 +363,26 @@ internal static IDocumentSnapshot CreateDocumentSnapshot(string path, ImmutableA }); return documentSnapshot.Object; } + + private static string FlipLineEndings(string input) + { + if (string.IsNullOrEmpty(input)) + { + return input; + } + + var hasCRLF = input.Contains("\r\n"); + var hasLF = !hasCRLF && input.Contains("\n"); + + if (hasCRLF) + { + return input.Replace("\r\n", "\n"); + } + else if (hasLF) + { + return input.Replace("\n", "\r\n"); + } + + return input; + } } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/HtmlFormattingTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/HtmlFormattingTest.cs index 6ef32d08326..300f2fc42c1 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/HtmlFormattingTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting_NetFx/HtmlFormattingTest.cs @@ -487,7 +487,8 @@ await RunFormattingTestAsync( } """, - tagHelpers: tagHelpers); + tagHelpers: tagHelpers, + skipFlipLineEndingTest: true); // tracked by https://github.com/dotnet/razor/issues/10836 } [Fact] @@ -595,7 +596,8 @@ await RunFormattingTestAsync( @{

} - """); + """, + skipFlipLineEndingTest: true); // tracked by https://github.com/dotnet/razor/issues/10836 } [Fact] @@ -1316,7 +1318,8 @@ await RunFormattingTestAsync( public bool VarBool { get; set; } } """, - fileKind: FileKinds.Component); + fileKind: FileKinds.Component, + skipFlipLineEndingTest: true); // tracked by https://github.com/dotnet/razor/issues/10836 } [Fact] @@ -1428,7 +1431,8 @@ await RunFormattingTestAsync( public bool VarBool { get; set; } } """, - fileKind: FileKinds.Component); + fileKind: FileKinds.Component, + skipFlipLineEndingTest: true); // tracked by https://github.com/dotnet/razor/issues/10836 } [Fact] @@ -1486,7 +1490,8 @@ await RunFormattingTestAsync( public bool VarBool { get; set; } } """, - fileKind: FileKinds.Component); + fileKind: FileKinds.Component, + skipFlipLineEndingTest: true); // tracked by https://github.com/dotnet/razor/issues/10836 } [Fact] @@ -1794,7 +1799,8 @@ await RunFormattingTestAsync( } """, - tagHelpers: CreateTagHelpers()); + tagHelpers: CreateTagHelpers(), + skipFlipLineEndingTest: true); // tracked by https://github.com/dotnet/razor/issues/10836 ImmutableArray CreateTagHelpers() {