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
154 changes: 154 additions & 0 deletions JavaToCSharp.Tests/EscapeIdentifierTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,160 @@ public void bar(List<String> items) {
Assert.Contains("(@object) => Print(@object)", parsed);
}

/// <summary>
/// Regression test for #133: the declaration of a local variable named after a C# keyword
/// was left unescaped even though its usages were escaped.
/// </summary>
[Fact]
public void ConvertText_GivenLocalVariableNamedAfterKeyword_ShouldEscapeDeclaration()
{
const string javaCode = """
public class Foo {
public void bar() {
InputStream in = openStream();
init(in);
}
}
""";

var parsed = Convert(javaCode);

Assert.Contains("InputStream @in = OpenStream();", parsed);
Assert.Contains("Init(@in);", parsed);
}

[Fact]
public void ConvertText_GivenTryWithResourcesNamedAfterKeyword_ShouldEscapeDeclaration()
{
const string javaCode = """
public class Foo {
public void bar(File dictionaryFile) {
try (InputStream in = new FileInputStream(dictionaryFile)) {
init(in);
}
}
}
""";

var parsed = Convert(javaCode);

Assert.Contains("using (InputStream @in = new FileInputStream(dictionaryFile))", parsed);
Assert.Contains("Init(@in);", parsed);
}

[Fact]
public void ConvertText_GivenFieldNamedAfterKeyword_ShouldEscapeDeclaration()
{
const string javaCode = """
public class Foo {
private int base = 1;
}
""";

var parsed = Convert(javaCode);

Assert.Contains("@base", parsed);
}

[Fact]
public void ConvertText_GivenForEachVariableNamedAfterKeyword_ShouldEscapeDeclaration()
{
const string javaCode = """
public class Foo {
public void bar(List<String> items) {
for (String object : items) {
print(object);
}
}
}
""";

var parsed = Convert(javaCode);

Assert.Contains("foreach (string @object in items)", parsed);
Assert.Contains("Print(@object);", parsed);
}

[Fact]
public void ConvertText_GivenForLoopVariableNamedAfterKeyword_ShouldEscapeDeclaration()
{
const string javaCode = """
public class Foo {
public void bar() {
for (int base = 0; base < 10; base++) {
print(base);
}
}
}
""";

var parsed = Convert(javaCode);

Assert.Contains("for (int @base = 0; @base < 10; @base++)", parsed);
Assert.Contains("Print(@base);", parsed);
}

[Fact]
public void ConvertText_GivenForLoopWithMultipleVariables_ShouldEscapeOnlyKeywords()
{
const string javaCode = """
public class Foo {
public void bar() {
for (int base = 0, i = 1; base < 10; base++) {
}
}
}
""";

var parsed = Convert(javaCode);

Assert.Contains("int @base = 0, i = 1;", parsed);
}

[Fact]
public void ConvertText_GivenCatchParameterNamedAfterKeyword_ShouldEscapeParameter()
{
const string javaCode = """
public class Foo {
public void bar() {
try {
baz();
} catch (Exception event) {
handle(event);
}
}
}
""";

var parsed = Convert(javaCode);

Assert.Contains("catch (Exception @event)", parsed);
Assert.Contains("Handle(@event);", parsed);
}

/// <summary>
/// Java 9+ allows an already-declared effectively-final variable as a try-with-resources
/// resource. That is a reference, not a declaration, but it still needs escaping.
/// </summary>
[Fact]
public void ConvertText_GivenTryWithResourcesNameExpressionNamedAfterKeyword_ShouldEscapeReference()
{
const string javaCode = """
public class Foo {
public void bar() throws Exception {
InputStream in = openStream();
try (in) {
init(in);
}
}
}
""";

var parsed = Convert(javaCode);

Assert.Contains("using (@in)", parsed);
}

/// <summary>
/// Escaped identifiers must survive a round trip through the C# parser without producing
/// diagnostics, which is what the original crash was really about.
Expand Down
4 changes: 2 additions & 2 deletions JavaToCSharp/Declarations/FieldDeclarationVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ public override MemberDeclarationSyntax VisitForClass(

if (initSyntax is not null)
{
var varDeclarationSyntax = SyntaxFactory.VariableDeclarator(name).WithInitializer(SyntaxFactory.EqualsValueClause(initSyntax));
var varDeclarationSyntax = SyntaxFactory.VariableDeclarator(TypeHelper.EscapeIdentifier(name)).WithInitializer(SyntaxFactory.EqualsValueClause(initSyntax));
variables.Add(varDeclarationSyntax);
}
}
else
variables.Add(SyntaxFactory.VariableDeclarator(name));
variables.Add(SyntaxFactory.VariableDeclarator(TypeHelper.EscapeIdentifier(name)));
}

var typeSyntax = TypeHelper.ConvertTypeSyntax(commonType, arrayRank ?? 0);
Expand Down
4 changes: 2 additions & 2 deletions JavaToCSharp/Statements/ExpressionStatementVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ private static StatementSyntax VisitVariableDeclarationStatement(ConversionConte
var initSyntax = ExpressionVisitor.VisitExpression(context, initExpr);
if (initSyntax is not null)
{
var varDeclarationSyntax = SyntaxFactory.VariableDeclarator(name).WithInitializer(SyntaxFactory.EqualsValueClause(initSyntax));
var varDeclarationSyntax = SyntaxFactory.VariableDeclarator(TypeHelper.EscapeIdentifier(name)).WithInitializer(SyntaxFactory.EqualsValueClause(initSyntax));
variables.Add(varDeclarationSyntax);
}
}
else
{
variables.Add(SyntaxFactory.VariableDeclarator(name));
variables.Add(SyntaxFactory.VariableDeclarator(TypeHelper.EscapeIdentifier(name)));
}
}

Expand Down
6 changes: 2 additions & 4 deletions JavaToCSharp/Statements/ForEachStatementVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,11 @@ public class ForEachStatementVisitor : StatementVisitor<ForEachStmt>
var type = TypeHelper.ConvertType(varType);

var variableDeclarators = varExpr.getVariables()?.ToList<VariableDeclarator>()?? [];
var vars = variableDeclarators
.Select(i => SyntaxFactory.VariableDeclarator(i.toString()))
.ToArray();
var identifier = SyntaxFactory.ParseToken(TypeHelper.EscapeIdentifier(variableDeclarators[0].getNameAsString()));

var body = foreachStmt.getBody();
var bodySyntax = VisitStatement(context, body);

return bodySyntax is null ? null : SyntaxFactory.ForEachStatement(SyntaxFactory.ParseTypeName(type), vars[0].Identifier.ValueText, iterableSyntax, bodySyntax);
return bodySyntax is null ? null : SyntaxFactory.ForEachStatement(SyntaxFactory.ParseTypeName(type), identifier, iterableSyntax, bodySyntax);
}
}
17 changes: 16 additions & 1 deletion JavaToCSharp/Statements/ForStatementVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,22 @@ public class ForStatementVisitor : StatementVisitor<ForStmt>

var variableDeclarators = varExpr.getVariables()?.ToList<VariableDeclarator>() ?? [];
var vars = variableDeclarators
.Select(i => SyntaxFactory.VariableDeclarator(i.toString()))
.Select(i =>
{
var declarator = SyntaxFactory.VariableDeclarator(TypeHelper.EscapeIdentifier(i.getNameAsString()));
var varInit = i.getInitializer().FromOptional<Expression>();

if (varInit is null)
{
return declarator;
}

var varInitSyntax = ExpressionVisitor.VisitExpression(context, varInit);

return varInitSyntax is null
? declarator
: declarator.WithInitializer(SyntaxFactory.EqualsValueClause(varInitSyntax));
})
.ToArray();

varSyntax = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName(type), SyntaxFactory.SeparatedList(vars, Enumerable.Repeat(SyntaxFactory.Token(SyntaxKind.CommaToken), vars.Length - 1)));
Expand Down
6 changes: 3 additions & 3 deletions JavaToCSharp/Statements/TryStatementVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public override StatementSyntax Visit(ConversionContext context, TryStmt tryStmt
{
result = SyntaxFactory.UsingStatement(result)
.WithExpression(
SyntaxFactory.IdentifierName(resource.asNameExpr().getNameAsString()));
SyntaxFactory.IdentifierName(TypeHelper.EscapeIdentifier(resource.asNameExpr().getNameAsString())));
}
else if (resource.isVariableDeclarationExpr())
{
Expand All @@ -63,7 +63,7 @@ public override StatementSyntax Visit(ConversionContext context, TryStmt tryStmt
SyntaxFactory.VariableDeclaration(
SyntaxFactory.ParseTypeName(TypeHelper.ConvertType(variable.getType())),
SyntaxFactory.SeparatedList([
SyntaxFactory.VariableDeclarator(variable.getNameAsString())
SyntaxFactory.VariableDeclarator(TypeHelper.EscapeIdentifier(variable.getNameAsString()))
.WithInitializer(SyntaxFactory.EqualsValueClause(initSyntax))
])
));
Expand Down Expand Up @@ -143,7 +143,7 @@ private static TryStatementSyntax AddCatches(ConversionContext context, CatchCla
SyntaxFactory.CatchClause(
SyntaxFactory.CatchDeclaration(
SyntaxFactory.ParseTypeName(type),
SyntaxFactory.ParseToken(ctch.getParameter().getNameAsString())
SyntaxFactory.ParseToken(TypeHelper.EscapeIdentifier(ctch.getParameter().getNameAsString()))
),
filter: null,
block: catchBlockSyntax
Expand Down
Loading