From be06031aef1394d8bd2c16fbf7817a9aa293a1f7 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Thu, 24 Sep 2026 11:50:16 -0700 Subject: [PATCH 1/2] module: don't map a trailing generated-only segment The mappings parser detects a segment that has only a generated column by peeking at the next character. At the end of the mappings string the peek returns an empty string, which is not a separator, so a final generated-only segment is decoded with phantom zero deltas and inherits the source, line, and column of the segment before it. With --enable-source-maps, stack frames in code after the last mapped segment are then attributed to the last mapped source. Check for the end of the string before reading source fields. Signed-off-by: Jacky Zhao --- lib/internal/source_map/source_map.js | 3 +- ...st-source-map-trailing-unmapped-segment.js | 48 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-source-map-trailing-unmapped-segment.js diff --git a/lib/internal/source_map/source_map.js b/lib/internal/source_map/source_map.js index 42e7bca3c4c5..fce0789a2e45 100644 --- a/lib/internal/source_map/source_map.js +++ b/lib/internal/source_map/source_map.js @@ -286,7 +286,8 @@ class SourceMap { } columnNumber += decodeVLQ(stringCharIterator); - if (isSeparator(stringCharIterator.peek())) { + if (!stringCharIterator.hasNext() || + isSeparator(stringCharIterator.peek())) { ArrayPrototypePush(this.#mappings, [lineNumber, columnNumber]); continue; } diff --git a/test/parallel/test-source-map-trailing-unmapped-segment.js b/test/parallel/test-source-map-trailing-unmapped-segment.js new file mode 100644 index 000000000000..847a27d93113 --- /dev/null +++ b/test/parallel/test-source-map-trailing-unmapped-segment.js @@ -0,0 +1,48 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const { SourceMap } = require('node:module'); + +// A segment with only a generated column marks the code after it as unmapped. +// When it is the last segment of the mappings, it must not inherit the +// original position of the segment before it. +const unmapped = { + originalSource: undefined, + originalLine: undefined, + originalColumn: undefined, + name: undefined, +}; + +// Trailing unmapped segment on the same line as the last mapped segment. +{ + const sm = new SourceMap({ + version: 3, + sources: ['a.js'], + names: [], + mappings: 'AAAA,K', + }); + + assert.deepStrictEqual(sm.findEntry(0, 5), { + generatedLine: 0, + generatedColumn: 5, + ...unmapped, + }); + assert.deepStrictEqual(sm.findOrigin(1, 6), {}); +} + +// Trailing unmapped segment on a later line. +{ + const sm = new SourceMap({ + version: 3, + sources: ['a.js'], + names: [], + mappings: 'AAAA;A', + }); + + assert.deepStrictEqual(sm.findEntry(1, 3), { + generatedLine: 1, + generatedColumn: 0, + ...unmapped, + }); + assert.deepStrictEqual(sm.findOrigin(2, 4), {}); +} From 9d71a74b530412e35e31d94e75b4e66559dece03 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Fri, 9 Oct 2026 11:08:23 -0700 Subject: [PATCH 2/2] fixup! module: don't map a trailing generated-only segment Check hasNext() before reading the optional name field, and add a test for a segment that has no name. Signed-off-by: Jacky Zhao Assisted-by: Claude Code --- lib/internal/source_map/source_map.js | 3 ++- ...st-source-map-trailing-unmapped-segment.js | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/internal/source_map/source_map.js b/lib/internal/source_map/source_map.js index fce0789a2e45..2bb23b00c167 100644 --- a/lib/internal/source_map/source_map.js +++ b/lib/internal/source_map/source_map.js @@ -301,7 +301,8 @@ class SourceMap { sourceColumnNumber += decodeVLQ(stringCharIterator); let name; - if (!isSeparator(stringCharIterator.peek())) { + if (stringCharIterator.hasNext() && + !isSeparator(stringCharIterator.peek())) { nameIndex += decodeVLQ(stringCharIterator); name = map.names?.[nameIndex]; } diff --git a/test/parallel/test-source-map-trailing-unmapped-segment.js b/test/parallel/test-source-map-trailing-unmapped-segment.js index 847a27d93113..d5c7243ad999 100644 --- a/test/parallel/test-source-map-trailing-unmapped-segment.js +++ b/test/parallel/test-source-map-trailing-unmapped-segment.js @@ -46,3 +46,22 @@ const unmapped = { }); assert.deepStrictEqual(sm.findOrigin(2, 4), {}); } + +// A segment without a name field must not pick up a name from the names array. +{ + const sm = new SourceMap({ + version: 3, + sources: ['a.js'], + names: ['should-not-be-used'], + mappings: 'AAAA', + }); + + assert.deepStrictEqual(sm.findEntry(0, 0), { + generatedLine: 0, + generatedColumn: 0, + originalSource: 'a.js', + originalLine: 0, + originalColumn: 0, + name: undefined, + }); +}