Repository navigation
Export stubs whose names aren't identifiers - #125
Merged
Merged
Conversation
The stub module for a mocked ES module declared each named export with
`export let ${name} = ...`, which is a syntax error when the name isn't an
identifier. Node 24+ gives CommonJS modules a 'module.exports' named
export, and testdouble.js's td.replaceEsm() passes every export of the
real module along when imitating it, so td.replaceEsm('some-cjs-package')
failed on Node 24+ with "SyntaxError: Unexpected token '.'".
Bind each stub to a local and export it under its name as a string
(`export { __quibbleExport0 as "module.exports" }`), which works for any
name. The name is also JSON-encoded when looking its stub up, so quotes
and backslashes in it no longer break the generated source either.
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.
This fixes the stub module quibble generates for a mock, so any export name works. Before, on Node 24+,
td.replaceEsm('some-cjs-package')failed withSyntaxError: Unexpected token '.'. The stub couldn't handle export names that aren't JavaScript identifiers, and Node 24+ gives every CommonJS module one called'module.exports'.Why it broke
When you mock an ES module, quibble generates the source of a stub module that exports your stubs. It declared each named export with
export let ${name} = …, so the name had to be a valid identifier.Node 24+ reports two exports when ES module code imports a CommonJS package:
defaultand'module.exports'. Node 22.14 and earlier report onlydefault. When testdouble.js'std.replaceEsm()is given no replacement, it imitates the real module and passes all of its export names toquibble.esm(). The stub then containedexport let module.exports = …, which is a syntax error.Calling
quibble.esm()directly with a name like'kebab-case'broke the same way, on any Node version.Why the fix works
JavaScript lets a module export a binding under any string name:
export { local as "any name" }. The stub now binds each stub value to a local variable and exports that under its real name, as a string:Importers see the real name, and the local name exists only inside the generated module. Each stub module has its own scope, so the locals can't clash across modules or mock generations. Because names are now JSON-encoded, quotes and backslashes in a name can't break the generated source either.
The change is all in
transformModuleSourceinlib/loader-helpers.js, which both the async hooks and the Node 26+ sync hooks use.New tests
The change adds two tests in
test/esm-lib/quibble-esm.test.mjs:'module.exports','kebab-case', and one with quotes and backslashes). This fails without the fix on every Node version.td.replaceEsm()does. This fails without the fix on Node 24+.