From 861b43294999d0a18d0d247c1a62425c7fdf6627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Sat, 25 Feb 2023 08:11:12 +0100 Subject: [PATCH] Update SA1513 codefix to use the existing newline character sequence #3360 --- .../LayoutRules/SA1513CodeFixProvider.cs | 3 +- .../Helpers/StringExtensions.cs | 23 ++++++++++++++ .../LayoutRules/SA1513UnitTests.cs | 21 +++++++++++++ .../StyleCop.Analyzers/Helpers/TokenHelper.cs | 30 +++++++++++++++++++ 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/StringExtensions.cs diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1513CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1513CodeFixProvider.cs index 1b38f0f3f..bea1d10e9 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1513CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1513CodeFixProvider.cs @@ -65,7 +65,8 @@ private static async Task GetTransformedDocumentAsync(Document docum diagnostics.Select(diagnostic => root.FindToken(diagnostic.Location.SourceSpan.End)), (originalToken, rewrittenToken) => { - var newTrivia = rewrittenToken.LeadingTrivia.Insert(0, SyntaxFactory.CarriageReturnLineFeed); + var endOfLineTrivia = rewrittenToken.GetPrecedingEndOfLineTrivia(); + var newTrivia = rewrittenToken.LeadingTrivia.Insert(0, endOfLineTrivia); return rewrittenToken.WithLeadingTrivia(newTrivia); }); } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/StringExtensions.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/StringExtensions.cs new file mode 100644 index 000000000..4c9404e6a --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/StringExtensions.cs @@ -0,0 +1,23 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.Helpers +{ + public static class StringExtensions + { + public static string ReplaceLineEndings(this string input, string replacementText) + { + // First normalize to LF + var lineFeedInput = input + .Replace("\r\n", "\n") + .Replace("\r", "\n") + .Replace("\f", "\n") + .Replace("\x0085", "\n") + .Replace("\x2028", "\n") + .Replace("\x2029", "\n"); + + // Then normalize to the replacement text + return lineFeedInput.Replace("\n", replacementText); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1513UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1513UnitTests.cs index 2bae9eade..267095142 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1513UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1513UnitTests.cs @@ -9,6 +9,7 @@ namespace StyleCop.Analyzers.Test.LayoutRules using System.Threading.Tasks; using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.LayoutRules; + using StyleCop.Analyzers.Test.Helpers; using Xunit; using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier< StyleCop.Analyzers.LayoutRules.SA1513ClosingBraceMustBeFollowedByBlankLine, @@ -1013,5 +1014,25 @@ public void TestMethod(string extraSupport) await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } + + [Fact] + [WorkItem(3360, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3360")] + public async Task TestLineFeedEndOfLinesAsync() + { + var testCode = @" +public class TestClass +{ +}[| +|]// Hello".ReplaceLineEndings("\n"); + + var fixedCode = @" +public class TestClass +{ +} + +// Hello".ReplaceLineEndings("\n"); + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/TokenHelper.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/TokenHelper.cs index 0f4e0bc76..9df1e5739 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/TokenHelper.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/TokenHelper.cs @@ -175,5 +175,35 @@ internal static bool IsFollowedByWhitespace(this SyntaxToken token) triviaList = token.GetNextToken().LeadingTrivia; return triviaList.Count > 0 && triviaList.First().IsKind(SyntaxKind.WhitespaceTrivia); } + + /// + /// Returns the closest end of line trivia preceding the . + /// This currently only looks immediately before the specified token. + /// + /// The token to process. + /// The closest preceding end of line trivia, or if none is found. + internal static SyntaxTrivia GetPrecedingEndOfLineTrivia(this SyntaxToken token) + { + var leadingTrivia = token.LeadingTrivia; + for (var i = leadingTrivia.Count - 1; i >= 0; i--) + { + if (leadingTrivia[i].IsKind(SyntaxKind.EndOfLineTrivia)) + { + return leadingTrivia[i]; + } + } + + var prevToken = token.GetPreviousToken(); + var prevTrailingTrivia = prevToken.TrailingTrivia; + for (var i = prevTrailingTrivia.Count - 1; i >= 0; i--) + { + if (prevTrailingTrivia[i].IsKind(SyntaxKind.EndOfLineTrivia)) + { + return prevTrailingTrivia[i]; + } + } + + return SyntaxFactory.CarriageReturnLineFeed; + } } }