lib: fix ERR_INVALID_ARG_TYPE with --enable-source-maps
Project: nodejs/node | Language: JavaScript | Concepts: error-handling, testing-strategy | Difficulty: mid
Link: https://github.com/nodejs/node/pull/63215
The problem
When Node.js is run with the --enable-source-maps flag, but a script has no source map (such as a plain .mjs or .cjs file), internal error location resolution could return undefined. This led to downstream code (notably in the assert module) receiving undefined for error messages, which caused a TypeError (ERR_INVALID_ARG_TYPE) to be thrown instead of the expected AssertionError.
Why it's non-trivial
A naive fix might just check for undefined and return a default message, but this would not preserve the intended behavior of showing the actual source line from the generated file when no source map is present. Additionally, all code paths where source lines could be missing needed to be handled, including the case where V8 fails to provide a source line at all.
The approach
The main logic change is in lib/internal/errors/error_source.js, where all code paths in getErrorSourceLocation() that previously returned undefined now fall back to using the generated source line and column. This matches the behavior when source maps are not enabled.
Guard if V8 fails to provide a source line:
+ if (!sourceLine) {
+ return;
+ }
This ensures that if V8 can't provide a source line at all, the function returns early. This is a last-resort guard.
If source maps are not enabled, return the generated source line:
if (!getSourceMapsSupport().enabled) {
return { sourceLine, startColumn };
}
This is unchanged, but is now guaranteed to be reached only if sourceLine is present.
If there is no source map for the file, use the generated source line:
const sm = findSourceMap(scriptResourceName);
if (sm === undefined) {
- return;
+ // No source map for this file; use the generated source line.
+ return { sourceLine, startColumn };
}
Previously, this would return undefined, causing downstream errors. Now, it falls back to the generated source.
If the source map exists but the original source is unavailable, use the generated source line:
const originalSourceLine = getSourceLine(sm, originalSource, originalLine, originalColumn);
if (!originalSourceLine) {
- return;
+ // Source map exists but original source is unavailable; use the
+ // generated source line rather than returning undefined.
+ return { sourceLine, startColumn };
}
This ensures that even if the source map is present but can't provide the original source line, the generated source is used as a fallback.
Test coverage:
Two new regression tests were added to ensure that both .mjs and .cjs files without source maps throw AssertionError (not TypeError) under --enable-source-maps:
test/fixtures/source-map/output/source_map_assert_no_source_map.mjstest/fixtures/source-map/output/source_map_assert_ok_no_source_map.cjs
The tests are registered in the test runner:
const tests = [
+ { name: 'source-map/output/source_map_assert_no_source_map.mjs' },
+ { name: 'source-map/output/source_map_assert_ok_no_source_map.cjs' },
{ name: 'source-map/output/source_map_disabled_by_api.js' },
...
];
From the review thread
atlowChemi: @kimjune01 I think the commit's module should be lib, not assert, as I don't think this changes assert specifically, that is mostly the symptom, not the actual issue, no?
kimjune01: Good point — the root cause is in the source map resolution path, not assert itself. I'll update the title.
Takeaways
- Always provide a consistent fallback for optional features (like source maps) to avoid breaking downstream error handling.
- Defensive programming: handle all code paths where a value could be missing, not just the "expected" ones.
- Regression tests for edge cases (e.g., missing source maps) are essential to prevent subtle bugs from returning.