Title: `--check` silently passes a `.js` file with a syntax error when the file contains ESM syntax · Issue #65202 · nodejs/node · GitHub
Open Graph Title: `--check` silently passes a `.js` file with a syntax error when the file contains ESM syntax · Issue #65202 · nodejs/node
X Title: `--check` silently passes a `.js` file with a syntax error when the file contains ESM syntax · Issue #65202 · nodejs/node
Description: Version v22.22.1 Platform Microsoft Windows NT 10.0.26200.0 x64 Subsystem module / cli (lib/internal/main/check_syntax.js) What steps will reproduce the bug? Create broken.js in a directory with no package.json: import fs from "node:fs";...
Open Graph Description: Version v22.22.1 Platform Microsoft Windows NT 10.0.26200.0 x64 Subsystem module / cli (lib/internal/main/check_syntax.js) What steps will reproduce the bug? Create broken.js in a directory with no...
X Description: Version v22.22.1 Platform Microsoft Windows NT 10.0.26200.0 x64 Subsystem module / cli (lib/internal/main/check_syntax.js) What steps will reproduce the bug? Create broken.js in a directory with no...
Opengraph URL: https://github.com/nodejs/node/issues/65202
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"`--check` silently passes a `.js` file with a syntax error when the file contains ESM syntax","articleBody":"### Version\n\nv22.22.1\n\n### Platform\n\n```text\nMicrosoft Windows NT 10.0.26200.0 x64\n```\n\n### Subsystem\n\nmodule / cli (`lib/internal/main/check_syntax.js`)\n\n### What steps will reproduce the bug?\n\nCreate `broken.js` in a directory with no `package.json`:\n\n```js\nimport fs from \"node:fs\";\nvar = ;\n```\n\nThen:\n\n```console\n$ node --check broken.js ; echo \"exit: $?\"\nexit: 0\n\n$ node broken.js\nvar = ;\n ^\nSyntaxError: Unexpected token '='\n```\n\n`--check` reports nothing and exits 0 for a file that is not valid JavaScript under either goal\nsymbol.\n\n### How often does it reproduce? Is there a required condition?\n\nAlways, given the condition: the file's format must be **ambiguous** — a `.js` extension with no\n`\"type\"` in the nearest `package.json` — and the source must contain `import` or `export`.\nEverything that states its format is handled correctly:\n\n| the file | `node --check` |\n| --- | --- |\n| `.mjs`, or `\"type\": \"module\"` | correct — reports the error |\n| `\"type\": \"commonjs\"` | correct — `Cannot use import statement outside a module` |\n| **`.js`, no `\"type\"`, containing `import`/`export`** | **exit 0, nothing reported** |\n| `.js` whose only ESM signal is top-level `await` | correct — reports the error |\n\n### What is the expected behavior? Why is that the expected behavior?\n\nA non-zero exit and the `SyntaxError`, as with every other invalid file. The documentation for\n`-c`, `--check` is \"Syntax check the script without executing\", with no caveat about module\ndetection — and the same source reports the error correctly the moment its format is stated.\n\nThis matters in practice because `node --check` is a common cheap gate in CI, and a plain `.js`\nfile containing `import` is the shape most projects have — so the sources most likely to be\nchecked are the ones that silently pass.\n\n### What do you see instead?\n\nExit code 0 and no output.\n\n### Additional information\n\n`lib/internal/main/check_syntax.js` parses under the Module goal only when `defaultGetFormat`\nanswers `'module'`, which an ambiguous `.js` does not:\n\n```js\nif (format === 'module') {\n const { ModuleWrap } = internalBinding('module_wrap');\n new ModuleWrap(filename, undefined, source, 0, 0);\n return;\n}\n\nwrapSafe(filename, source, undefined, format);\n```\n\nSo the CommonJS branch runs. Inside `wrapSafe`, `cjsModuleInstance` is `undefined` here, so\n`shouldDetectModule` falls through to `--require-module` — on by default — and\n`compileFunctionForCJSLoader` *detects* ESM instead of throwing. Its result is then discarded, and\nthe source is never parsed under either goal.\n\n`Module.prototype._compile` handles exactly this case correctly a little further down the same\nfile, by reading the flag the detection returns:\n\n```js\nif (format !== 'module') {\n const result = wrapSafe(filename, content, this, format);\n compiledWrapper = result.function;\n if (result.canParseAsESM) {\n format = 'module';\n }\n}\n\nif (format === 'module') {\n loadESMFromCJS(this, filename, format, content);\n return;\n}\n```\n\n`checkSyntax` looks like it wants the same `canParseAsESM` branch, falling back to the `ModuleWrap`\nparse it already has above rather than returning.\n\nThe relevant part of `check_syntax.js` is unchanged on `main`; the file was last touched\n2024-12-02. The behaviour most likely dates from `module: unflag detect-module` (2024-07-20), which\nmade detection the default for this path.\n\n**Test coverage.** `test/sequential/test-cli-syntax-bad.js` checks `bad_syntax.js`, `bad_syntax`,\n`bad_syntax_shebang.js` and `bad_syntax_shebang` — all CommonJS.\n`test/fixtures/syntax/bad_syntax.mjs` exists but is used only by `test-esm-error-cache.js` and\n`test-compile-cache-bad-syntax.js`, not by either `--check` test. So neither a `.mjs` file nor a\ndetected-ESM `.js` file is currently asserted to fail `--check`.\n\nI am happy to open a PR with the fix and a fixture plus test if that shape is welcome.\n\n\u003csub>Found while differential-testing Node against\n[ViperJS](https://github.com/MerlijnW70/viperjs), an embeddable JavaScript engine in safe Rust.\nSweeping eight repositories, the two disagreed about 18 files; every one of them turned out to be\ngenuinely invalid JavaScript, and `node --check` had reported nine as fine. V8 itself is right\nabout all eighteen — it is only `--check` that never asks it.\u003c/sub>\n","author":{"url":"https://github.com/MerlijnW70","@type":"Person","name":"MerlijnW70"},"datePublished":"2026-08-10T20:55:10Z","url":"https://github.com/nodejs/node/issues/65202"}
| react-profiling | 0 |
| react-import-map | react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf |
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:0c725ef8-d502-d0ed-5140-d90d7a273369 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 9AD2:1F5F5F:11C5408:1799F55:6AED58BB |
| html-safe-nonce | c73461e3454981b8fb8d458ae0edee6622f41b0c368393721dc9eb8099a3156c |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5QUQyOjFGNUY1RjoxMUM1NDA4OjE3OTlGNTU6NkFFRDU4QkIiLCJ2aXNpdG9yX2lkIjoiNTc4Mzg0MzM0NzYxMjEyOTQ2NyIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | 8b73785844d76a8800dcd9a246bbf83a0240926cabd0472b0362d7bde36f2947 |
| hovercard-subject-tag | issue:5114279303 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/nodejs/node/65202/issue_layout |
| twitter:image | https://opengraph.githubassets.com/051ea5bdccb497f1b414841042c7514efbb4b4e664d0ec68af43e2f2fd07ced3/nodejs/node/issues/65202 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/051ea5bdccb497f1b414841042c7514efbb4b4e664d0ec68af43e2f2fd07ced3/nodejs/node/issues/65202 |
| og:image:alt | Version v22.22.1 Platform Microsoft Windows NT 10.0.26200.0 x64 Subsystem module / cli (lib/internal/main/check_syntax.js) What steps will reproduce the bug? Create broken.js in a directory with no... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | MerlijnW70 |
| hostname | github.com |
| expected-hostname | github.com |
| None | 56fb42677ae87ed3e64410323d67980aa65d0485568a7858f874e7edfc6d1c11 |
| turbo-cache-control | no-preview |
| go-import | github.com/nodejs/node git https://github.com/nodejs/node.git |
| octolytics-dimension-user_id | 9950313 |
| octolytics-dimension-user_login | nodejs |
| octolytics-dimension-repository_id | 27193779 |
| octolytics-dimension-repository_nwo | nodejs/node |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 27193779 |
| octolytics-dimension-repository_network_root_nwo | nodejs/node |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 9c9a37e2423ed382cf39f70b5aa4b4858b40a3b0 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
| react-app-name | issues |
Links:
Viewport: width=device-width