From be06031aef1394d8bd2c16fbf7817a9aa293a1f7 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Thu, 24 Sep 2026 11:50:16 -0700 Subject: [PATCH] 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), {}); +}