Escape variable declarations named after C# keywords (#133) - #156
Merged
Merged
Conversation
Usages of a variable were escaped via NameExpressionVisitor, but the declarations themselves were not, producing uncompilable output such as `InputStream in = ...` alongside a correctly escaped `Init(@in)`. Apply TypeHelper.EscapeIdentifier at the four declarator sites: local variables, fields, try-with-resources resources, and foreach variables. The foreach visitor previously round-tripped the name through a VariableDeclarator and read back Identifier.ValueText, which strips the `@` prefix; it now builds the identifier token directly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to the previous commit, covering the two remaining declaration sites that left keyword identifiers unescaped. The for-loop initializer passed VariableDeclarator.toString() — the name *and* its initializer, e.g. "i = 0" — into SyntaxFactory.VariableDeclarator, which treats the whole string as an identifier. Escaping that blindly corrupts the output, so build the declarator from its parts instead: the escaped name plus the initializer visited as an expression. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Java 9+ form `try (existingVar)` references an already-declared effectively-final variable rather than declaring one, and was the last identifier site left unescaped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #133.
Problem
TypeHelper.EscapeIdentifierwas applied at identifier usages —NameExpressionVisitor,FieldAccessExpressionVisitor— and at parameters, but never at any declaration site. So a variable named after a C# keyword produced output that referenced an identifier it never declared:That C# does not compile. Seven sites were affected, all of the same shape.
Fix
Applied
EscapeIdentifierat every declaration site:ExpressionStatementVisitor.csFieldDeclarationVisitor.csTryStatementVisitor.cstry (existingVar)resources (Java 9+)TryStatementVisitor.csTryStatementVisitor.csForEachStatementVisitor.csForStatementVisitor.csTwo needed more than an added call:
ForEachStatementVisitorbuilt aVariableDeclaratoronly to readIdentifier.ValueTextback off it, which strips the@prefix. It now constructs the identifier token directly, dropping the pointless round trip.ForStatementVisitorpassedVariableDeclarator.toString()intoSyntaxFactory.VariableDeclarator. That returns the name and its initializer ("i = 0"), while that overload treats the whole string as an identifier name — it happened to round-trip through the printer, which is why it worked. Escaping it blindly corrupts the output, so the declarator is now built from its parts: the escaped name plus the initializer visited throughExpressionVisitor, matching how every other declarator site works.Testing
Written TDD — each test was added and confirmed failing before the corresponding fix. Eight new tests in
EscapeIdentifierTests.cscover local variables, fields, try-with-resources (both forms), catch parameters, foreach, for-loop init, and a multi-declarator for loop (int @base = 0, i = 1) asserting that only the keyword is escaped.The
ForStatementVisitorrewrite is a behavior change on a hot path, so coverage there matters beyond the unit tests:Resources/*.javacontains dozens of classicforloops, andFullIntegrationTestsRoslyn-compiles the generated C# and executes it. Those pass, which is what establishes the rewrite is faithful rather than merely printer-identical.Scope note
Only the first commit is #133 as filed. The for-loop, catch-clause, and
try (existingVar)sites are the same class of bug, found while fixing it, and are split into their own commits — they drop cleanly onto a separate branch if you'd rather keep #133 narrow.🤖 Generated with Claude Code