Make generated JavaScript identifiers hygienic

Open
#286 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
35/100
Issue type
Refactor
Clarity
Mostly clear
Activity status
Active
Tech stack
javascript
Domain
tooling

Research direction

Start by reading lib/constructs/operation.js, lib/constructs/interface.js, lib/parameters.js, the callback constructs, utils.formatArgs(), RequiresMap, and lib/transformer.js. Generate and run cases covering colliding arguments, imports, callbacks, constructors, processors, and strict-mode names. Done means generated code parses and runs without binding capture or redeclaration while preserving function length, conversions, errors, and implementation calls.

Written by the indexing model from the issue text.

Description

Created by GPT-5.6 Sol Extra High at the maintainer's request.

Problem

webidl2js does not consistently separate names derived from Web IDL from names owned by the JavaScript generator. Valid Web IDL identifiers can therefore collide with generated locals, captured bindings, imports, or strict-mode restrictions. Depending on the name and construct, the generated module can fail to parse or can run with subtly incorrect bindings.

This came up while investigating wrapper branding in #284 and #285. New generated code needed an interface descriptor and a fused receiver implementation local. An operation argument named interfaceDescriptor shadowed an unprefixed module binding, so the experimental implementation used $interfaceDescriptor and $impl: $ is valid in JavaScript identifiers but not in Web IDL identifiers. That prevents those specific collisions, but the same underlying problem already exists elsewhere in the generator.

For example, all of these argument names are valid Web IDL:

[Exposed=Window]
interface Example {
  undefined hasDuplicateArgs(any args);
  undefined hasDuplicateESValue(any esValue);
  undefined shadowsRealm(any globalObject);
  undefined shadowsType(Other Other);
};

callback ExampleCallback = undefined (any thisArg);

Current generation can produce failures equivalent to:

hasDuplicateArgs(args) {
  const args = []; // SyntaxError
}

hasDuplicateESValue(esValue) {
  const esValue = this; // SyntaxError
}

shadowsRealm(globalObject) {
  // References intended for the captured realm now resolve to the argument.
}

shadowsType(Other) can shadow a generated const Other = require("./Other.js"), causing conversion code to call .convert() on the argument instead of the generated interface module. Callback-function and callback-interface adapters similarly declare locals such as thisArg, callResult, O, and X in scopes whose formal parameters come directly from Web IDL.

There are several related surfaces to audit:

  • lib/constructs/operation.js and constructor generation in lib/constructs/interface.js use effective-overload argument names as JavaScript formal parameters, while lib/parameters.js introduces fixed locals including args and curArg.
  • Instance operation and attribute bodies capture fixed names such as globalObject, utils, conversions, Impl, exports, and generated interface-module bindings. A same-named formal parameter can shadow them even when it does not cause a redeclaration error.
  • lib/constructs/callback-function.js and lib/constructs/callback-interface.js use Web IDL argument names directly and also introduce their own fixed locals. Some callback paths do not go through utils.formatArgs() at all.
  • utils.formatArgs() suffixes JavaScript keywords, but does not prevent collisions with generator-owned bindings. Its keyword set also does not cover strict-mode restricted binding names such as eval and arguments.
  • RequiresMap detects conflicting generated require keys within the map, but fixed module declarations (conversions, utils, and Impl) are added separately by lib/transformer.js and are invisible to that check. Required Web IDL type names can therefore collide with fixed declarations instead of receiving aliases.
  • Generated interface and namespace names are emitted as JavaScript bindings. For example, an interface named globalObject would collide with the globalObject parameter surrounding its generated class declaration.
  • Attribute, iterable, async-iterable, legacy-platform-object, dictionary, enumeration, mixin, namespace, callback, and processor-injected code should all be audited rather than assuming the operation examples above are exhaustive. Processor hooks should receive allocated binding references instead of having to guess or reserve names independently.

A quick scan of the current jsdom Web IDL corpus found no collision with the audited fixed local names, which likely explains why this has not appeared in downstream tests. The problematic inputs are nevertheless valid Web IDL.

Suggested direction

Introduce a single identifier-allocation/hygiene policy for generated lexical bindings instead of fixing collisions one at a time.

In particular:

  • Do not use Web IDL argument spellings as JavaScript formal parameter names where only arity matters. Regular operations and constructors already read values from arguments, so formals such as $0, $1, etc. can preserve the required function length without exposing IDL names as bindings.
  • For callbacks, where the current body refers to formal parameters, allocate safe formals by index and maintain an explicit mapping from each IDL argument to its generated binding.
  • Allocate or reserve all generator-owned locals, captures, class bindings, and imports consistently. A $-prefixed namespace is attractive because Web IDL cannot supply such identifiers, but this should be centralized rather than applied ad hoc. A lexical-scope-aware name allocator is another option.
  • Route RequiresMap and the fixed transformer imports through the same allocation mechanism, including collisions caused by path-to-key normalization.
  • Keep original Web IDL names where they are semantically observable or diagnostic—property keys and error messages—but not as untrusted lexical binding names.
  • Give processor hooks the actual allocated identifiers they may reference.

The existing $interfaceDescriptor/$impl approach should be understood as a local mitigation and a useful test case, not as completion of this work.

Tests

Regression coverage should generate, load, and exercise code rather than relying only on snapshots. It should cover at least:

  • Operation and constructor arguments named args, curArg, esValue, globalObject, utils, conversions, Impl, exports, and the name of a referenced interface type.
  • Callback-function and callback-interface arguments named thisArg, callResult, value, context, Reflect, O, and X.
  • Strict-mode restricted names such as eval and arguments, plus JavaScript keywords currently handled by formatArgs().
  • Web IDL interface/type names that collide with fixed imports, generated class/install locals, and normalized RequiresMap keys.
  • Non-overloaded and overloaded operations, static and namespace operations, constructors, callbacks, and generated code both with and without processors.
  • Preservation of specified function length, argument conversion behavior, realm-correct errors, and calls into implementations.

The end state should make it mechanically impossible for a valid Web IDL identifier to capture, redeclare, or alter the meaning of a generator-owned JavaScript binding.

Dominant language
JavaScript
Stars
98
Forks
35
Avg merge
2h 13m
Merged PRs (30d)
9

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from jsdom/webidl2js

All issues in jsdom/webidl2js

Similar issues

More JavaScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.