Skip to content

Commit cc356ff

Browse files
committed
esm: improve check for ESM syntax
1 parent a0a5b75 commit cc356ff

File tree

3 files changed

+31
-26
lines changed

3 files changed

+31
-26
lines changed

lib/internal/modules/cjs/loader.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ const {
9898
const {
9999
getCjsConditions,
100100
initializeCjsConditions,
101-
hasEsmSyntax,
101+
isModuleSyntaxError,
102102
loadBuiltinModule,
103103
makeRequireFunction,
104104
normalizeReferrerURL,
@@ -1393,10 +1393,20 @@ Module._extensions['.js'] = function(module, filename) {
13931393
const pkg = packageJsonReader.readPackageScope(filename) || { __proto__: null };
13941394
// Function require shouldn't be used in ES modules.
13951395
if (pkg.data?.type === 'module') {
1396+
// This is an error path, because `require` of a `.js` file in a `"type": "module"` scope is not allowed.
13961397
const parent = moduleParentCache.get(module);
13971398
const parentPath = parent?.filename;
13981399
const packageJsonPath = path.resolve(pkg.path, 'package.json');
1399-
const usesEsm = hasEsmSyntax(content);
1400+
1401+
let usesEsm = false;
1402+
try {
1403+
internalCompileFunction(content, ['exports', 'require', 'module', '__filename', '__dirname'], { filename });
1404+
} catch (compilationError) {
1405+
if (isModuleSyntaxError(compilationError)) {
1406+
usesEsm = true;
1407+
}
1408+
}
1409+
14001410
const err = new ERR_REQUIRE_ESM(filename, usesEsm, parentPath,
14011411
packageJsonPath);
14021412
// Attempt to reconstruct the parent require frame.

lib/internal/modules/esm/translators.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ const {
44
ArrayPrototypeMap,
55
Boolean,
66
JSONParse,
7-
ObjectGetPrototypeOf,
87
ObjectPrototypeHasOwnProperty,
98
ObjectKeys,
109
ReflectApply,
@@ -15,7 +14,6 @@ const {
1514
StringPrototypeReplaceAll,
1615
StringPrototypeSlice,
1716
StringPrototypeStartsWith,
18-
SyntaxErrorPrototype,
1917
globalThis: { WebAssembly },
2018
} = primordials;
2119

@@ -33,7 +31,7 @@ const assert = require('internal/assert');
3331
const { readFileSync } = require('fs');
3432
const { dirname, extname, isAbsolute } = require('path');
3533
const {
36-
hasEsmSyntax,
34+
isModuleSyntaxError,
3735
loadBuiltinModule,
3836
stripBOM,
3937
} = require('internal/modules/helpers');
@@ -168,8 +166,7 @@ translators.set('module', async function moduleStrategy(url, source, isMain) {
168166
* @param {string} [filename] Useful only if `content` is unknown.
169167
*/
170168
function enrichCJSError(err, content, filename) {
171-
if (err != null && ObjectGetPrototypeOf(err) === SyntaxErrorPrototype &&
172-
hasEsmSyntax(content || readFileSync(filename, 'utf-8'))) {
169+
if (isModuleSyntaxError(err)) {
173170
// Emit the warning synchronously because we are in the middle of handling
174171
// a SyntaxError that will throw and likely terminate the process before an
175172
// asynchronous warning would be emitted.

lib/internal/modules/helpers.js

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
const {
44
ArrayPrototypeForEach,
55
ArrayPrototypeJoin,
6-
ArrayPrototypeSome,
76
ObjectDefineProperty,
7+
ObjectGetPrototypeOf,
88
ObjectPrototypeHasOwnProperty,
99
SafeMap,
1010
SafeSet,
1111
StringPrototypeCharCodeAt,
1212
StringPrototypeIncludes,
1313
StringPrototypeSlice,
1414
StringPrototypeStartsWith,
15+
SyntaxErrorPrototype,
1516
} = primordials;
1617
const {
1718
ERR_INVALID_ARG_TYPE,
@@ -300,31 +301,28 @@ function normalizeReferrerURL(referrer) {
300301
}
301302

302303
/**
303-
* For error messages only, check if ESM syntax is in use.
304-
* @param {string} code
304+
* Check if the error is a syntax error due to ESM syntax in CommonJS.
305+
* - `import` statements return an error with a message `Cannot use import statement outside a module`.
306+
* - `export` statements return an error with a message `Unexpected token 'export'`.
307+
* - `import.meta` returns an error with a message `Cannot use 'import.meta' outside a module`.
308+
* Top-level `await` currently returns the same error message as when `await` is used in a sync function,
309+
* so we don't use it as a disambiguation.
310+
* Dynamic `import()` is permitted in CommonJS, so we don't use it as a disambiguation.
311+
* @param {Error} err
305312
*/
306-
function hasEsmSyntax(code) {
307-
debug('Checking for ESM syntax');
308-
const parser = require('internal/deps/acorn/acorn/dist/acorn').Parser;
309-
let root;
310-
try {
311-
root = parser.parse(code, { sourceType: 'module', ecmaVersion: 'latest' });
312-
} catch {
313-
return false;
314-
}
315-
316-
return ArrayPrototypeSome(root.body, (stmt) =>
317-
stmt.type === 'ExportDefaultDeclaration' ||
318-
stmt.type === 'ExportNamedDeclaration' ||
319-
stmt.type === 'ImportDeclaration' ||
320-
stmt.type === 'ExportAllDeclaration');
313+
function isModuleSyntaxError(err) {
314+
return err != null && ObjectGetPrototypeOf(err) === SyntaxErrorPrototype && (
315+
err.message === 'Cannot use import statement outside a module' ||
316+
err.message === "Unexpected token 'export'" ||
317+
err.message === "Cannot use 'import.meta' outside a module"
318+
);
321319
}
322320

323321
module.exports = {
324322
addBuiltinLibsToObject,
325323
getCjsConditions,
326324
initializeCjsConditions,
327-
hasEsmSyntax,
325+
isModuleSyntaxError,
328326
loadBuiltinModule,
329327
makeRequireFunction,
330328
normalizeReferrerURL,

0 commit comments

Comments
 (0)