Files
node/lib/internal/main/check_syntax.js
T
Paul Bouchon 1b2de5e052 module: fix --check on ambiguous ESM files
A `.js` file with no `"type"` in the nearest package.json has no format
of its own, and `defaultGetFormat()` reports it as null. `--check`
passed that null straight to `wrapSafe()`, which parses as CommonJS.
Module syntax makes that parse bail out early, so the file was reported
as valid and `--check` exited 0 even though it is not valid JavaScript
under either goal.

At load time the goal for such a file is decided by looking for module
syntax in the source. Decide it the same way here, so the file is
parsed as a module and its real syntax error is reported. Files whose
format is known are unaffected, as are ambiguous files without module
syntax, which are still parsed as CommonJS.

Fixes: https://github.com/nodejs/node/issues/65202
Signed-off-by: Paul Bouchon <mail@bitpshr.net>
PR-URL: https://github.com/nodejs/node/pull/65203
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Aviv Keller <me@aviv.sh>
2026-08-12 21:47:11 +00:00

90 lines
2.6 KiB
JavaScript

'use strict';
// If user passed `-c` or `--check` arguments to Node, check its syntax
// instead of actually running the file.
const { getOptionValue } = require('internal/options');
const { URL, pathToFileURL } = require('internal/url');
const {
prepareMainThreadExecution,
markBootstrapComplete,
} = require('internal/process/pre_execution');
const {
readStdin,
} = require('internal/process/execution');
const {
Module: {
_resolveFilename: resolveCJSModuleName,
},
wrapSafe,
} = require('internal/modules/cjs/loader');
// TODO(joyeecheung): not every one of these are necessary
prepareMainThreadExecution(true);
if (process.argv[1] && process.argv[1] !== '-') {
// Expand process.argv[1] into a full path.
const path = require('path');
process.argv[1] = path.resolve(process.argv[1]);
// Read the source.
const filename = resolveCJSModuleName(process.argv[1]);
const fs = require('fs');
const source = fs.readFileSync(filename, 'utf-8');
markBootstrapComplete();
loadESMIfNeeded(() => checkSyntax(source, filename));
} else {
markBootstrapComplete();
loadESMIfNeeded(() => readStdin((code) => {
checkSyntax(code, '[stdin]');
}));
}
function loadESMIfNeeded(cb) {
const hasModulePreImport = getOptionValue('--import').length > 0;
if (hasModulePreImport) {
require('internal/modules/run_main').runEntryPointWithESMLoader(cb);
return;
}
cb();
}
async function checkSyntax(source, filename) {
let format;
if (filename === '[stdin]' || filename === '[eval]') {
format = (getOptionValue('--input-type') === 'module') ? 'module' : 'commonjs';
} else {
const { defaultResolve } = require('internal/modules/esm/resolve');
const { defaultGetFormat } = require('internal/modules/esm/get_format');
const { url } = await defaultResolve(pathToFileURL(filename).toString());
format = await defaultGetFormat(new URL(url));
}
// A `.js` file with no `"type"` in the nearest package.json has no format of
// its own. At load time the goal is decided by looking for module syntax in
// the source, so decide it the same way here. Otherwise such a file is only
// ever parsed as CommonJS, where module syntax makes the parse bail out
// before any syntax error in the rest of the file is reported.
if (format === null || format === undefined) {
const { containsModuleSyntax } = internalBinding('contextify');
if (containsModuleSyntax(source, filename)) {
format = 'module';
}
}
if (format === 'module') {
const { ModuleWrap } = internalBinding('module_wrap');
new ModuleWrap(filename, undefined, source, 0, 0);
return;
}
wrapSafe(filename, source, undefined, format);
}