Skip to content

Commit a45e219

Browse files
committed
Detect ambiguous files and string input
1 parent b788745 commit a45e219

File tree

4 files changed

+63
-20
lines changed

4 files changed

+63
-20
lines changed

lib/internal/modules/esm/get_format.js

+20-5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const experimentalNetworkImports =
2121
const defaultTypeFlag = getOptionValue('--experimental-default-type');
2222
// The next line is where we flip the default to ES modules someday.
2323
const defaultType = defaultTypeFlag === 'module' ? 'module' : 'commonjs';
24+
const { containsModuleSyntax } = internalBinding('contextify');
2425
const { getPackageType } = require('internal/modules/esm/resolve');
2526
const { fileURLToPath } = require('internal/url');
2627
const { ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes;
@@ -90,20 +91,34 @@ function underNodeModules(url) {
9091
* @returns {string}
9192
*/
9293
function getFileProtocolModuleFormat(url, context, ignoreErrors) {
94+
const { source } = context;
9395
const ext = extname(url);
9496

9597
if (ext === '.js') {
9698
const packageType = getPackageType(url);
9799
if (packageType !== 'none') {
98100
return packageType;
99101
}
102+
100103
// The controlling `package.json` file has no `type` field.
101-
if (defaultType === 'module') {
102-
// An exception to the type flag making ESM the default everywhere is that package scopes under `node_modules`
103-
// should retain the assumption that a lack of a `type` field means CommonJS.
104-
return underNodeModules(url) ? 'commonjs' : 'module';
104+
switch (getOptionValue('--experimental-default-type')) {
105+
case 'module': { // The user explicitly passed `--experimental-default-type=module`.
106+
// An exception to the type flag making ESM the default everywhere is that package scopes under `node_modules`
107+
// should retain the assumption that a lack of a `type` field means CommonJS.
108+
return underNodeModules(url) ? 'commonjs' : 'module';
109+
}
110+
case 'commonjs': { // The user explicitly passed `--experimental-default-type=commonjs`.
111+
return 'commonjs';
112+
}
113+
default: { // The user did not pass `--experimental-default-type`.
114+
// `source` is undefined when this is called from `defaultResolve`;
115+
// but this gets called again from `defaultLoad`/`defaultLoadSync`.
116+
if (source && getOptionValue('--experimental-detect-module')) {
117+
return containsModuleSyntax(`${source}`, fileURLToPath(url)) ? 'module' : 'commonjs';
118+
}
119+
return 'commonjs';
120+
}
105121
}
106-
return 'commonjs';
107122
}
108123

109124
if (ext === '') {

lib/internal/modules/esm/load.js

+20-14
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const DATA_URL_PATTERN = /^[^/]+\/[^,;]+(?:[^,]*?)(;base64)?,([\s\S]*)$/;
3333
/**
3434
* @param {URL} url URL to the module
3535
* @param {ESModuleContext} context used to decorate error messages
36-
* @returns {{ responseURL: string, source: string | BufferView }}
36+
* @returns {Promise<{ responseURL: string, source: string | BufferView }>}
3737
*/
3838
async function getSource(url, context) {
3939
const { protocol, href } = url;
@@ -127,19 +127,24 @@ async function defaultLoad(url, context = kEmptyObject) {
127127

128128
throwIfUnsupportedURLScheme(urlInstance, experimentalNetworkImports);
129129

130-
format ??= await defaultGetFormat(urlInstance, context);
131-
132-
validateAttributes(url, format, importAttributes);
133-
134-
if (
135-
format === 'builtin' ||
136-
format === 'commonjs'
137-
) {
130+
if (urlInstance.protocol === 'node:') {
138131
source = null;
139132
} else if (source == null) {
140133
({ responseURL, source } = await getSource(urlInstance, context));
134+
context.source = source;
135+
}
136+
137+
if (format == null || format === 'commonjs') {
138+
// Now that we have the source for the module, run `defaultGetFormat` again in case we detect ESM syntax.
139+
format = await defaultGetFormat(urlInstance, context);
140+
}
141+
142+
if (format === 'commonjs') {
143+
source = null; // Let the CommonJS loader handle it (for now)
141144
}
142145

146+
validateAttributes(url, format, importAttributes);
147+
143148
return {
144149
__proto__: null,
145150
format,
@@ -178,16 +183,17 @@ function defaultLoadSync(url, context = kEmptyObject) {
178183

179184
throwIfUnsupportedURLScheme(urlInstance, false);
180185

181-
format ??= defaultGetFormat(urlInstance, context);
182-
183-
validateAttributes(url, format, importAttributes);
184-
185-
if (format === 'builtin') {
186+
if (urlInstance.protocol === 'node:') {
186187
source = null;
187188
} else if (source == null) {
188189
({ responseURL, source } = getSourceSync(urlInstance, context));
190+
context.source = source;
189191
}
190192

193+
format ??= defaultGetFormat(urlInstance, context);
194+
195+
validateAttributes(url, format, importAttributes);
196+
191197
return {
192198
__proto__: null,
193199
format,

lib/internal/modules/run_main.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ const {
44
StringPrototypeEndsWith,
55
} = primordials;
66

7+
const { containsModuleSyntax } = internalBinding('contextify');
78
const { getOptionValue } = require('internal/options');
9+
const fs = require('fs');
810
const path = require('path');
911

1012
/**
@@ -70,7 +72,19 @@ function shouldUseESMLoader(mainPath) {
7072
const { readPackageScope } = require('internal/modules/package_json_reader');
7173
const pkg = readPackageScope(mainPath);
7274
// No need to guard `pkg` as it can only be an object or `false`.
73-
return pkg.data?.type === 'module' || getOptionValue('--experimental-default-type') === 'module';
75+
switch (pkg.data?.type) {
76+
case 'module':
77+
return true;
78+
case 'commonjs':
79+
return false;
80+
default: { // No package.json or no `type` field.
81+
if (getOptionValue('--experimental-detect-module')) {
82+
const content = fs.readFileSync(mainPath, 'utf8');
83+
return containsModuleSyntax(content, mainPath);
84+
}
85+
return false;
86+
}
87+
}
7488
}
7589

7690
/**

lib/internal/process/execution.js

+8
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ const {
2626
emitAfter,
2727
popAsyncContext,
2828
} = require('internal/async_hooks');
29+
const { containsModuleSyntax } = internalBinding('contextify');
30+
const { getOptionValue } = require('internal/options');
2931
const {
3032
makeContextifyScript, runScriptInThisContext,
3133
} = require('internal/vm');
@@ -70,6 +72,12 @@ function evalScript(name, body, breakFirstLine, print, shouldLoadESM = false) {
7072
const baseUrl = pathToFileURL(module.filename).href;
7173
const { loadESM } = asyncESM;
7274

75+
if (getOptionValue('--experimental-detect-module') &&
76+
getOptionValue('--input-type') === '' && getOptionValue('--experimental-default-type') === '' &&
77+
containsModuleSyntax(body, name)) {
78+
return evalModule(body, print);
79+
}
80+
7381
const runScript = () => {
7482
// Create wrapper for cache entry
7583
const script = `

0 commit comments

Comments
 (0)