Skip to content

Commit 399917c

Browse files
committed
Detect ESM syntax in --eval
1 parent f353af7 commit 399917c

File tree

3 files changed

+63
-13
lines changed

3 files changed

+63
-13
lines changed

lib/internal/modules/helpers.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ const {
55
ArrayPrototypeJoin,
66
ArrayPrototypeSome,
77
ObjectDefineProperty,
8+
ObjectGetPrototypeOf,
89
ObjectPrototypeHasOwnProperty,
910
SafeMap,
1011
SafeSet,
1112
StringPrototypeCharCodeAt,
1213
StringPrototypeIncludes,
1314
StringPrototypeSlice,
1415
StringPrototypeStartsWith,
16+
SyntaxErrorPrototype,
1517
} = primordials;
1618
const {
1719
ERR_INVALID_ARG_TYPE,
@@ -320,10 +322,29 @@ function hasEsmSyntax(code) {
320322
stmt.type === 'ExportAllDeclaration');
321323
}
322324

325+
/**
326+
* Check if the error is a syntax error due to ESM syntax in CommonJS.
327+
* - `import` statements return an error with a message `Cannot use import statement outside a module`.
328+
* - `export` statements return an error with a message `Unexpected token 'export'`.
329+
* - `import.meta` returns an error with a message `Cannot use 'import.meta' outside a module`.
330+
* Top-level `await` currently returns the same error message as when `await` is used in a sync function,
331+
* so we don't use it as a disambiguation.
332+
* Dynamic `import()` is permitted in CommonJS, so we don't use it as a disambiguation.
333+
* @param {Error} err
334+
*/
335+
function isModuleSyntaxError(err) {
336+
return err != null && ObjectGetPrototypeOf(err) === SyntaxErrorPrototype && (
337+
err.message === 'Cannot use import statement outside a module' ||
338+
err.message === "Unexpected token 'export'" ||
339+
err.message === "Cannot use 'import.meta' outside a module"
340+
);
341+
}
342+
323343
module.exports = {
324344
addBuiltinLibsToObject,
325345
getCjsConditions,
326346
initializeCjsConditions,
347+
isModuleSyntaxError,
327348
hasEsmSyntax,
328349
loadBuiltinModule,
329350
makeRequireFunction,

lib/internal/process/execution.js

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -79,19 +79,30 @@ function evalScript(name, body, breakFirstLine, print, shouldLoadESM = false) {
7979
`;
8080
globalThis.__filename = name;
8181
RegExpPrototypeExec(/^/, ''); // Necessary to reset RegExp statics before user code runs.
82-
const result = module._compile(script, `${name}-wrapper`)(() =>
83-
require('vm').runInThisContext(body, {
84-
filename: name,
85-
displayErrors: true,
86-
[kVmBreakFirstLineSymbol]: !!breakFirstLine,
87-
importModuleDynamically(specifier, _, importAssertions) {
88-
const loader = asyncESM.esmLoader;
89-
return loader.import(specifier, baseUrl, importAssertions);
90-
},
91-
}));
92-
if (print) {
93-
const { log } = require('internal/console/global');
94-
log(result);
82+
try {
83+
const result = module._compile(script, `${name}-wrapper`)(() =>
84+
require('vm').runInThisContext(body, {
85+
filename: name,
86+
displayErrors: true,
87+
[kVmBreakFirstLineSymbol]: !!breakFirstLine,
88+
importModuleDynamically(specifier, _, importAssertions) {
89+
const loader = asyncESM.esmLoader;
90+
return loader.import(specifier, baseUrl, importAssertions);
91+
},
92+
}));
93+
if (print) {
94+
const { log } = require('internal/console/global');
95+
log(result);
96+
}
97+
} catch (error) {
98+
const { getOptionValue } = require('internal/options');
99+
if (getOptionValue('--experimental-detect-module') && getOptionValue('--input-type') === '' && getOptionValue('--experimental-default-type') === '') {
100+
const { isModuleSyntaxError } = require('internal/modules/helpers');
101+
if (isModuleSyntaxError(error)) {
102+
return evalModule(body, print);
103+
}
104+
}
105+
throw error;
95106
}
96107

97108
if (origModule !== undefined)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { spawnPromisified } from '../common/index.mjs';
2+
import { describe, it } from 'node:test';
3+
import { strictEqual } from 'node:assert';
4+
5+
describe('--experimental-detect-module', () => {
6+
it('permits ESM syntax in --eval input without requiring --input-type=module', async () => {
7+
const { stdout, stderr, code, signal } = await spawnPromisified(process.execPath, [
8+
'--experimental-detect-module',
9+
'--eval',
10+
'import { version } from "node:process"; console.log(version);',
11+
]);
12+
13+
strictEqual(stderr, '');
14+
strictEqual(stdout, `${process.version}\n`);
15+
strictEqual(code, 0);
16+
strictEqual(signal, null);
17+
});
18+
})

0 commit comments

Comments
 (0)