From b23beae13385f60fd5f8d1c90a9a9171b5fdefa5 Mon Sep 17 00:00:00 2001 From: def-Param Date: Sat, 31 Oct 2026 10:33:16 +0530 Subject: [PATCH 1/2] fix(indexed): apply lineShift before comparing with next section offset In IndexedSourceMapConsumer.generatedPositionFor, allGeneratedPositionsFor, and eachMapping, the section-boundary check compared the section-relative generated line against the next section's absolute generated line offset before applying lineShift. This meant the clamp never triggered for sections with a nonzero line offset, leaving lastColumn as Infinity. Fixes #542 and #539. --- lib/source-map-consumer.js | 9 ++-- test/test-source-map-consumer.js | 72 ++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/lib/source-map-consumer.js b/lib/source-map-consumer.js index 17ef92b1..c886d24c 100644 --- a/lib/source-map-consumer.js +++ b/lib/source-map-consumer.js @@ -921,7 +921,8 @@ class IndexedSourceMapConsumer extends SourceMapConsumer { if ( generatedPosition.lastColumn === Infinity && nextSection && - generatedPosition.line === nextSection.generatedOffset.generatedLine + generatedPosition.line + lineShift === + nextSection.generatedOffset.generatedLine ) { generatedPosition.lastColumn = nextSection.generatedOffset.generatedColumn - 2; @@ -964,7 +965,8 @@ class IndexedSourceMapConsumer extends SourceMapConsumer { if ( generatedPosition.lastColumn === Infinity && nextSection && - generatedPosition.line === nextSection.generatedOffset.generatedLine + generatedPosition.line + lineShift === + nextSection.generatedOffset.generatedLine ) { generatedPosition.lastColumn = nextSection.generatedOffset.generatedColumn - 2; @@ -997,7 +999,8 @@ class IndexedSourceMapConsumer extends SourceMapConsumer { if ( mapping.lastGeneratedColumn === Infinity && nextSection && - mapping.generatedLine === nextSection.generatedOffset.generatedLine + mapping.generatedLine + lineShift === + nextSection.generatedOffset.generatedLine ) { mapping.lastGeneratedColumn = nextSection.generatedOffset.generatedColumn - 2; diff --git a/test/test-source-map-consumer.js b/test/test-source-map-consumer.js index 8cf50988..6c79c80c 100644 --- a/test/test-source-map-consumer.js +++ b/test/test-source-map-consumer.js @@ -1559,6 +1559,78 @@ exports["test computeColumnSpans"] = async function (assert) { map.destroy(); }; +exports["test computeColumnSpans with nonzero line offset sections (#542)"] = async function (assert) { + const section = source => ({ version: 3, sources: [source], names: [], mappings: "AAAA" }); + + const map = await new SourceMapConsumer({ + version: 3, + sections: [ + { offset: { line: 0, column: 0 }, map: section("a.js") }, + { offset: { line: 2, column: 0 }, map: section("b.js") }, + { offset: { line: 2, column: 10 }, map: section("c.js") }, + ], + }); + map.computeColumnSpans(); + + const pos = map.generatedPositionFor({ source: "b.js", line: 1, column: 0 }); + assert.equal(pos.line, 3); + assert.equal(pos.column, 0); + assert.equal(pos.lastColumn, 9); + + const all = map.allGeneratedPositionsFor({ source: "b.js", line: 1, column: 0 }); + assert.equal(all.length, 1); + assert.equal(all[0].line, 3); + assert.equal(all[0].column, 0); + assert.equal(all[0].lastColumn, 9); + + const eachMappings = []; + map.eachMapping(m => eachMappings.push(m)); + const bMapping = eachMappings.find(m => m.source === "b.js"); + assert.equal(bMapping.lastGeneratedColumn, 9); + + map.destroy(); +}; + +exports["test computeColumnSpans with first section nonzero line offset (#539)"] = async function (assert) { + const consumer = await new SourceMapConsumer({ + version: 3, + sections: [ + { + offset: { line: 4, column: 3 }, + map: { version: 3, sources: ["first.js"], names: [], mappings: "AAAA" }, + }, + { + offset: { line: 4, column: 10 }, + map: { version: 3, sources: ["second.js"], names: [], mappings: "AAAA" }, + }, + ], + }); + try { + consumer.computeColumnSpans(); + const pos = consumer.generatedPositionFor({ + source: "first.js", line: 1, column: 0, + }); + assert.equal(pos.line, 5); + assert.equal(pos.column, 3); + assert.equal(pos.lastColumn, 9); + + const all = consumer.allGeneratedPositionsFor({ + source: "first.js", line: 1, column: 0, + }); + assert.equal(all.length, 1); + assert.equal(all[0].line, 5); + assert.equal(all[0].column, 3); + assert.equal(all[0].lastColumn, 9); + + const eachMappings = []; + consumer.eachMapping(m => eachMappings.push(m)); + const firstMapping = eachMappings.find(m => m.source === "first.js"); + assert.equal(firstMapping.lastGeneratedColumn, 9); + } finally { + consumer.destroy(); + } +}; + exports["test sourceRoot + originalPositionFor"] = async function (assert) { let map = new SourceMapGenerator({ sourceRoot: "foo/bar", From c3ceea43161ee5582e0f468719beaf23eb1fe9bc Mon Sep 17 00:00:00 2001 From: def-Param Date: Sat, 31 Oct 2026 11:49:23 +0530 Subject: [PATCH 2/2] fix(consumer): guard against null source in _findSourceIndex and sourceContentFor (#450) Add null/undefined checks at the start of _findSourceIndex and both sourceContentFor implementations. When aSource is null or undefined (e.g. from an unprocessed HTTP query parameter), return -1 or null instead of potentially throwing 'Cannot read property indexOf of null'. Fixes #450. --- lib/source-map-consumer.js | 9 +++++++++ test/test-source-map-consumer.js | 2 ++ 2 files changed, 11 insertions(+) diff --git a/lib/source-map-consumer.js b/lib/source-map-consumer.js index c886d24c..b46d7ec1 100644 --- a/lib/source-map-consumer.js +++ b/lib/source-map-consumer.js @@ -238,6 +238,9 @@ class BasicSourceMapConsumer extends SourceMapConsumer { * found. */ _findSourceIndex(aSource) { + if (aSource == null) { + return -1; + } // In the most common usecases, we'll be constantly looking up the index for the same source // files, so we cache the index lookup to avoid constantly recomputing the full URLs. const cachedIndex = this._sourceLookupCache.get(aSource); @@ -534,6 +537,9 @@ class BasicSourceMapConsumer extends SourceMapConsumer { * available. */ sourceContentFor(aSource, nullOnMissing) { + if (aSource == null) { + return null; + } if (!this.sourcesContent) { return null; } @@ -855,6 +861,9 @@ class IndexedSourceMapConsumer extends SourceMapConsumer { * available. */ sourceContentFor(aSource, nullOnMissing) { + if (aSource == null) { + return null; + } for (let i = 0; i < this._sections.length; i++) { const section = this._sections[i]; diff --git a/test/test-source-map-consumer.js b/test/test-source-map-consumer.js index 6c79c80c..49481765 100644 --- a/test/test-source-map-consumer.js +++ b/test/test-source-map-consumer.js @@ -945,6 +945,8 @@ exports["test that we can get the original sources for the sources"] = assert.throws(function () { map.sourceContentFor("three.js"); }, Error); + assert.equal(map.sourceContentFor(null), null); + assert.equal(map.sourceContentFor(undefined), null); map.destroy(); };