From afe981e96d00597af675e1380eb1b899669ad251 Mon Sep 17 00:00:00 2001 From: Travis Jones <31292511+CosmonautJones@users.noreply.github.com> Date: Fri, 23 Oct 2026 07:19:23 -0400 Subject: [PATCH] Fix escaping of VB multiline string literals --- CodeConverter/CSharp/LiteralConversions.cs | 2 +- .../ExpressionTests/StringExpressionTests.cs | 59 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/CodeConverter/CSharp/LiteralConversions.cs b/CodeConverter/CSharp/LiteralConversions.cs index 50af9f1a6..d3a82f02a 100644 --- a/CodeConverter/CSharp/LiteralConversions.cs +++ b/CodeConverter/CSharp/LiteralConversions.cs @@ -102,7 +102,7 @@ internal static string EscapeQuotes(string unquotedTextForUser, string valueText return EscapeVerbatimQuotes(valueTextForCompiler); } - return unquotedTextForUser.Replace("\"\"", "\\\""); + return SymbolDisplay.FormatLiteral(valueTextForCompiler, false).Replace("\"", "\\\""); } public static string EscapeVerbatimQuotes(string valueTextForCompiler) diff --git a/Tests/CSharp/ExpressionTests/StringExpressionTests.cs b/Tests/CSharp/ExpressionTests/StringExpressionTests.cs index 09481594b..1078904a3 100644 --- a/Tests/CSharp/ExpressionTests/StringExpressionTests.cs +++ b/Tests/CSharp/ExpressionTests/StringExpressionTests.cs @@ -1,4 +1,7 @@ using System.Threading.Tasks; +using ICSharpCode.CodeConverter.Common; +using ICSharpCode.CodeConverter.CSharp; +using ICSharpCode.CodeConverter.Tests.Compilation; using ICSharpCode.CodeConverter.Tests.TestRunners; using Xunit; @@ -6,6 +9,62 @@ namespace ICSharpCode.CodeConverter.Tests.CSharp.ExpressionTests; public class StringExpressionTests : ConverterTestBase { + [Fact] + public async Task NewlineOnlyStringAsync() + { + await TestConversionVisualBasicToCSharpAsync( + "Class TestClass\r\n Private Sub TestMethod()\r\n" + + " Dim tmp = \"\r\n\"\r\n End Sub\r\nEnd Class", @" +internal partial class TestClass +{ + private void TestMethod() + { + string tmp = ""\r\n""; + } +}"); + } + + [Theory] + [InlineData("\r\n")] + [InlineData("\n")] + [InlineData("\r")] + [InlineData("\r\n\r\n")] + [InlineData("first\nsecond")] + [InlineData("first\rsecond")] + [InlineData("quote \" and backslash \\\nend")] + [InlineData("quote \" and backslash \\\r\nend")] + [InlineData("")] + [InlineData("plain \"quoted\" text")] + [InlineData(@"C:\new\folder")] + public async Task StringLiteralPreservesValueAsync(string value) + { + var source = "Public Class C\r\n Public Const Value As String = \"" + + value.Replace("\"", "\"\"") + "\"\r\nEnd Class"; + var converted = await ConvertAsync(source); + var compiler = new CSharpCompiler(); + var assembly = compiler.AssemblyFromCode(compiler.CreateTree(converted)); + Assert.Equal(value, assembly.GetType("C").GetField("Value").GetRawConstantValue()); + } + + [Theory] + [InlineData("\r\n")] + [InlineData("\n")] + [InlineData("\r")] + [InlineData("\r\n\r\n")] + [InlineData("\"{braces}\\\n")] + public async Task InterpolatedMultilineStringPreservesValueAsync(string value) + { + var source = "Public Class C\r\n Public Shared Function GetValue() As String\r\n" + + " Dim v1 = 1\r\n Dim v2 = 2\r\n" + + " Return $\"What's up with {v1}" + + value.Replace("\"", "\"\"").Replace("{", "{{").Replace("}", "}}") + + "{v2}\"\r\n End Function\r\nEnd Class"; + var converted = await ConvertAsync(source); + var compiler = new CSharpCompiler(); + var assembly = compiler.AssemblyFromCode(compiler.CreateTree(converted)); + Assert.Equal("What's up with 1" + value + "2", assembly.GetType("C").GetMethod("GetValue").Invoke(null, null)); + } + [Fact] public async Task MultilineStringAsync() {