Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions JavaToCSharp.Tests/IntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public void GeneralUnsuccessfulConversionTest(string filePath)
[InlineData("Resources/Java9PrivateInterfaceMethods.java")]
[InlineData("Resources/Java10TypeInference.java")]
[InlineData("Resources/Java14SwitchExpressions.java")]
[InlineData("Resources/Java15TextBlocks.java")]
[InlineData("Resources/NewArrayLiteralBug.java")]
[InlineData("Resources/OctalLiteralBug.java")]
[InlineData("Resources/DeprecatedAnnotation.java")]
Expand Down
33 changes: 33 additions & 0 deletions JavaToCSharp.Tests/Resources/Java15TextBlocks.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/// Expect:
/// - output: "<html>\n <body>hi</body>\n</html>\n|he said \"\"quoted\"\" ok\n|a b\tc\n"
package example;

// https://docs.oracle.com/en/java/javase/15/language/text-blocks.html

public class Program {
public static void main(String[] args) {
String html = """
<html>
<body>hi</body>
</html>
""";

// Two adjacent quotes are legal inside a text block, and require a longer
// delimiter when converted to a C# raw string literal.
String quotes = """
he said ""quoted"" ok
""";

// \s keeps a trailing space, a trailing backslash joins lines, and \t is a tab.
String escapes = """
a\s\
b\tc
""";

System.out.print(html);
System.out.print("|");
System.out.print(quotes);
System.out.print("|");
System.out.print(escapes);
}
}
17 changes: 17 additions & 0 deletions JavaToCSharp.Tests/VisitLiteralExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,23 @@ public void VisitLiteralExpression_String()
Assert.Equal("\\r", expr?.GetFirstToken().ValueText);
}

[Theory]
// The value is written as it appears in Java source, indented under the opening """.
[InlineData(" a\n b\n ", "a\nb\n")]
[InlineData(" <html>\n <body>hi</body>\n </html>\n ", "<html>\n <body>hi</body>\n</html>\n")]
// Escapes are resolved once: \t stays a tab and \\ collapses to a single backslash.
[InlineData(" tab\there \\\\ backslash\n ", "tab\there \\ backslash\n")]
// Two adjacent quotes need a four-quote delimiter in the generated C#.
[InlineData(" he said \"\"quoted\"\" ok\n ", "he said \"\"quoted\"\" ok\n")]
public void VisitLiteralExpression_TextBlock(string javaValue, string expected)
{
var expr = ExpressionVisitor.VisitExpression(
new ConversionContext(new JavaConversionOptions()),
new TextBlockLiteralExpr(javaValue));

Assert.Equal(expected, expr?.GetFirstToken().ValueText);
}

[Theory]
[InlineData("0b10", 2)]
[InlineData("0b100", 4)]
Expand Down
1 change: 1 addition & 0 deletions JavaToCSharp/Expressions/ExpressionVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ static ExpressionVisitor()
{ typeof(MethodReferenceExpr), new MethodReferenceExpressionVisitor() },
{ typeof(TypeExpr), new TypeExpressionVisitor() },
{ typeof(SwitchExpr), new SwitchExpressionVisitor() },
{ typeof(TextBlockLiteralExpr), new TextBlockLiteralExpressionVisitor() },
};
}

Expand Down
57 changes: 57 additions & 0 deletions JavaToCSharp/Expressions/TextBlockLiteralExpressionVisitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using com.github.javaparser.ast.expr;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace JavaToCSharp.Expressions;

public class TextBlockLiteralExpressionVisitor : ExpressionVisitor<TextBlockLiteralExpr>
{
protected override ExpressionSyntax Visit(ConversionContext context, TextBlockLiteralExpr expr)
{
// asString() applies the Java text block rules for us: incidental whitespace is
// stripped and escape sequences (including \s and line continuations) are resolved,
// so the result is the final string value and must not be unescaped again.
var value = expr.asString();

return SyntaxFactory.LiteralExpression(
SyntaxKind.StringLiteralExpression,
CreateRawStringLiteral(value));
}

private static SyntaxToken CreateRawStringLiteral(string value)
{
// C# requires the delimiter to be longer than the longest run of quotes in the
// content, so text containing "" needs at least four quotes to fence it.
var fence = new string('"', Math.Max(3, LongestQuoteRun(value) + 1));

// The opening fence is followed by a newline, and the closing fence sits on its own
// line. That final newline belongs to the delimiter rather than the value, so a Java
// text block ending in a newline needs the value written out verbatim before it.
var text = $"{fence}\n{value}\n{fence}";

return SyntaxFactory.Token(
SyntaxTriviaList.Empty,
SyntaxKind.MultiLineRawStringLiteralToken,
text,
value,
SyntaxTriviaList.Empty);
}

private static int LongestQuoteRun(string value)
{
int longest = 0, run = 0;

foreach (var c in value)
{
run = c == '"' ? run + 1 : 0;

if (run > longest)
{
longest = run;
}
}

return longest;
}
}
Loading