Skip to content

Commit

Permalink
Detect ESM syntax in --eval
Browse files Browse the repository at this point in the history
  • Loading branch information
GeoffreyBooth committed Oct 9, 2023
1 parent f353af7 commit 399917c
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 13 deletions.
21 changes: 21 additions & 0 deletions lib/internal/modules/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ const {
ArrayPrototypeJoin,
ArrayPrototypeSome,
ObjectDefineProperty,
ObjectGetPrototypeOf,
ObjectPrototypeHasOwnProperty,
SafeMap,
SafeSet,
StringPrototypeCharCodeAt,
StringPrototypeIncludes,
StringPrototypeSlice,
StringPrototypeStartsWith,
SyntaxErrorPrototype,
} = primordials;
const {
ERR_INVALID_ARG_TYPE,
Expand Down Expand Up @@ -320,10 +322,29 @@ function hasEsmSyntax(code) {
stmt.type === 'ExportAllDeclaration');
}

/**
* Check if the error is a syntax error due to ESM syntax in CommonJS.
* - `import` statements return an error with a message `Cannot use import statement outside a module`.
* - `export` statements return an error with a message `Unexpected token 'export'`.
* - `import.meta` returns an error with a message `Cannot use 'import.meta' outside a module`.
* Top-level `await` currently returns the same error message as when `await` is used in a sync function,
* so we don't use it as a disambiguation.
* Dynamic `import()` is permitted in CommonJS, so we don't use it as a disambiguation.
* @param {Error} err
*/
function isModuleSyntaxError(err) {
return err != null && ObjectGetPrototypeOf(err) === SyntaxErrorPrototype && (
err.message === 'Cannot use import statement outside a module' ||
err.message === "Unexpected token 'export'" ||
err.message === "Cannot use 'import.meta' outside a module"
);
}

module.exports = {
addBuiltinLibsToObject,
getCjsConditions,
initializeCjsConditions,
isModuleSyntaxError,
hasEsmSyntax,
loadBuiltinModule,
makeRequireFunction,
Expand Down
37 changes: 24 additions & 13 deletions lib/internal/process/execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,19 +79,30 @@ function evalScript(name, body, breakFirstLine, print, shouldLoadESM = false) {
`;
globalThis.__filename = name;
RegExpPrototypeExec(/^/, ''); // Necessary to reset RegExp statics before user code runs.
const result = module._compile(script, `${name}-wrapper`)(() =>
require('vm').runInThisContext(body, {
filename: name,
displayErrors: true,
[kVmBreakFirstLineSymbol]: !!breakFirstLine,
importModuleDynamically(specifier, _, importAssertions) {
const loader = asyncESM.esmLoader;
return loader.import(specifier, baseUrl, importAssertions);
},
}));
if (print) {
const { log } = require('internal/console/global');
log(result);
try {
const result = module._compile(script, `${name}-wrapper`)(() =>
require('vm').runInThisContext(body, {
filename: name,
displayErrors: true,
[kVmBreakFirstLineSymbol]: !!breakFirstLine,
importModuleDynamically(specifier, _, importAssertions) {
const loader = asyncESM.esmLoader;
return loader.import(specifier, baseUrl, importAssertions);
},
}));
if (print) {
const { log } = require('internal/console/global');
log(result);
}
} catch (error) {
const { getOptionValue } = require('internal/options');
if (getOptionValue('--experimental-detect-module') && getOptionValue('--input-type') === '' && getOptionValue('--experimental-default-type') === '') {
const { isModuleSyntaxError } = require('internal/modules/helpers');
if (isModuleSyntaxError(error)) {
return evalModule(body, print);
}
}
throw error;
}

if (origModule !== undefined)
Expand Down
18 changes: 18 additions & 0 deletions test/es-module/test-esm-detect-ambiguous.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { spawnPromisified } from '../common/index.mjs';
import { describe, it } from 'node:test';
import { strictEqual } from 'node:assert';

describe('--experimental-detect-module', () => {
it('permits ESM syntax in --eval input without requiring --input-type=module', async () => {
const { stdout, stderr, code, signal } = await spawnPromisified(process.execPath, [
'--experimental-detect-module',
'--eval',
'import { version } from "node:process"; console.log(version);',
]);

strictEqual(stderr, '');
strictEqual(stdout, `${process.version}\n`);
strictEqual(code, 0);
strictEqual(signal, null);
});
})

0 comments on commit 399917c

Please sign in to comment.