Skip to content

Escape keyword-named bindings in the JavaScript generator [patch] - #462

Open
matt-edmondson wants to merge 2 commits into
mainfrom
fix/106-javascript-keyword-identifiers
Open

matt-edmondson wants to merge 2 commits into
mainfrom
fix/106-javascript-keyword-identifiers

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Part of #106. JavaScript was the last target the issue's triage listed as still writing keyword-named identifiers verbatim.

What was wrong

The JavaScript generator had no reserved-word set, so a keyword-named parameter, local, function, class or top-level name came out as written. For example, total(in, typeof), function return(with) and let new = ... all fail to parse (SyntaxError: Unexpected token 'in').

Design decision

The last triage comment on #106 left this open: JavaScript accepts a keyword as a member name after a . (holder.default, this.switch()), and as a field, accessor or method name. The shared EscapeReference and SpellCallee, however, escaped every segment. This PR takes the option that renames as little as possible:

  • Bindings are escaped with an underscore, at the declaration and at every use: parameters, locals, free functions, classes (including a nested class expression's own name), top-level let/const, and enum and alias constants. extends, new and an alias's value go through EscapeReference too.
  • Members keep their names. Fields, accessors, methods, enumeration members and object-literal keys are written as declared, so the object's shape is unchanged.

Changes

  • LanguageGeneratorBase: a new protected virtual bool EscapesMemberNames => true. When a target returns false:

    • EscapeReference escapes only a reference's leading segment.
    • A call with a receiver writes its callee verbatim.
    • SpellCallee counts only free functions, because methods weren't escaped.

    Every other generator keeps the default, so their output is unchanged.

  • JavaScriptGenerator:

    • Adds ReservedWords: the reserved words, the strict-mode future reserved words, the literals, and arguments/eval, which strict code can't bind. Class bodies and modules are strict.
    • Adds IsReceiverWord (this, super, true, false, null) and EscapesMemberNames => false.
    • Routes every binding site through EscapeIdentifier.

Tests

New file Coder.Test/Languages/JavaScriptReservedWordIdentifierTests.cs. The tests live in their own file so this PR and #445 (Go), which both extend the shared test file, merge in either order. I checked with git merge-tree that this branch merges cleanly with both #445 and #446.

  • JavaScript_KeywordNamesRunAndResolve runs the generated script under node with a driver. The driver reads back holder.class, holder.default, a get delete() accessor, total(in_, typeof_) with locals let_/new_, this.switch() called from another method, Flag.null, return_(…) called from relay(), a top-level var_, and class Child extends import_.
  • JavaScript_ReferenceEscapesTheBindingButNotTheMember checks the text: this.class, this.switch() and get delete() are unescaped, and there is no this_, class_, switch_ or null_.

With the two generator files reverted, both tests fail: node reports SyntaxError: Unexpected token 'in', and the text assertion fails. With the change, the full suite passes: 1666/1666 on net10.0, Linux.

Left open on #106

From the earlier triage: in C, the function-pointer member (*name) of an interface's vtable struct still isn't escaped.


Generated by Claude Code

JavaScript wrote keyword-named parameters, locals, functions, classes and
top-level names verbatim, so `total(in, typeof)` or `function return(...)`
failed to parse. These bindings now get an underscore (`in_`, `return_`)
at the declaration and at every use.

Members keep their names. JavaScript reads a keyword after a `.` and as a
field, accessor or method name, so `holder.default` and `this.switch()`
stay as written. A new `EscapesMemberNames` hook on LanguageGeneratorBase
tells EscapeReference to escape only a reference's leading segment, and
SpellCallee to leave a call with a receiver alone and to count only free
functions.

Part of #106.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW3ect9msbMq1zFsRLD
Keeps ReservedWordIdentifierTests.cs untouched, so this branch and the Go
keyword PR (#445) merge in either order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW3ect9msbMq1zFsRLD
@sonarqubecloud

sonarqubecloud Bot commented Nov 7, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants