Skip to content

Commit

Permalink
Merge branch 'master' into gh-4424_throw_watch_change
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert committed Mar 5, 2022
2 parents a112db8 + 994c1ec commit 46dc17f
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 49 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,17 @@
# rollup changelog

## 2.69.1

_2022-03-04_

### Bug Fixes

- Approximate source position instead of ignoring it when using a low-resolution source map in a transform hook (#4334)

### Pull Requests

- [#4334](https://github.com/rollup/rollup/pull/4334): fix(sourcemap): fall back to low-resolution line mapping (@aleclarson and @lukastaegert)

## 2.69.0

_2022-03-02_
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "rollup",
"version": "2.69.0",
"version": "2.69.1",
"description": "Next-generation ES module bundler",
"main": "dist/rollup.js",
"module": "dist/es/rollup.js",
Expand Down
61 changes: 32 additions & 29 deletions src/utils/collapseSourcemaps.ts
Expand Up @@ -47,6 +47,7 @@ class Link {

traceMappings() {
const sources: string[] = [];
const sourceIndexMap = new Map<string, number>();
const sourcesContent: string[] = [];
const names: string[] = [];
const nameIndexMap = new Map<string, number>();
Expand All @@ -57,7 +58,7 @@ class Link {
const tracedLine: SourceMapSegment[] = [];

for (const segment of line) {
if (segment.length == 1) continue;
if (segment.length === 1) continue;
const source = this.sources[segment[1]];
if (!source) continue;

Expand All @@ -68,36 +69,34 @@ class Link {
);

if (traced) {
// newer sources are more likely to be used, so search backwards.
let sourceIndex = sources.lastIndexOf(traced.source.filename);
if (sourceIndex === -1) {
const {
column,
line,
name,
source: { content, filename }
} = traced;
let sourceIndex = sourceIndexMap.get(filename);
if (sourceIndex === undefined) {
sourceIndex = sources.length;
sources.push(traced.source.filename);
sourcesContent[sourceIndex] = traced.source.content;
sources.push(filename);
sourceIndexMap.set(filename, sourceIndex);
sourcesContent[sourceIndex] = content;
} else if (sourcesContent[sourceIndex] == null) {
sourcesContent[sourceIndex] = traced.source.content;
} else if (
traced.source.content != null &&
sourcesContent[sourceIndex] !== traced.source.content
) {
sourcesContent[sourceIndex] = content;
} else if (content != null && sourcesContent[sourceIndex] !== content) {
return error({
message: `Multiple conflicting contents for sourcemap source ${traced.source.filename}`
message: `Multiple conflicting contents for sourcemap source ${filename}`
});
}

const tracedSegment: SourceMapSegment = [
segment[0],
sourceIndex,
traced.line,
traced.column
];
const tracedSegment: SourceMapSegment = [segment[0], sourceIndex, line, column];

if (traced.name) {
let nameIndex = nameIndexMap.get(traced.name);
if (name) {
let nameIndex = nameIndexMap.get(name);
if (nameIndex === undefined) {
nameIndex = names.length;
names.push(traced.name);
nameIndexMap.set(traced.name, nameIndex);
names.push(name);
nameIndexMap.set(name, nameIndex);
}

(tracedSegment as SourceMapSegment)[4] = nameIndex;
Expand All @@ -118,13 +117,17 @@ class Link {
if (!segments) return null;

// binary search through segments for the given column
let i = 0;
let j = segments.length - 1;
let searchStart = 0;
let searchEnd = segments.length - 1;

while (i <= j) {
const m = (i + j) >> 1;
while (searchStart <= searchEnd) {
const m = (searchStart + searchEnd) >> 1;
const segment = segments[m];
if (segment[0] === column) {

// If a sourcemap does not have sufficient resolution to contain a
// necessary mapping, e.g. because it only contains line information, we
// use the best approximation we could find
if (segment[0] === column || searchStart === searchEnd) {
if (segment.length == 1) return null;
const source = this.sources[segment[1]];
if (!source) return null;
Expand All @@ -136,9 +139,9 @@ class Link {
);
}
if (segment[0] > column) {
j = m - 1;
searchEnd = m - 1;
} else {
i = m + 1;
searchStart = m + 1;
}
}

Expand Down
29 changes: 12 additions & 17 deletions src/utils/getOriginalLocation.ts
Expand Up @@ -2,35 +2,30 @@ import type { DecodedSourceMapOrMissing, ExistingDecodedSourceMap } from '../rol

export function getOriginalLocation(
sourcemapChain: readonly DecodedSourceMapOrMissing[],
location: { column: number; line: number; name?: string; source?: string }
location: { column: number; line: number }
): { column: number; line: number } {
const filteredSourcemapChain = sourcemapChain.filter(
(sourcemap): sourcemap is ExistingDecodedSourceMap => !!sourcemap.mappings
);

while (filteredSourcemapChain.length > 0) {
traceSourcemap: while (filteredSourcemapChain.length > 0) {
const sourcemap = filteredSourcemapChain.pop()!;
const line = sourcemap.mappings[location.line - 1];
let locationFound = false;

if (line !== undefined) {
for (const segment of line) {
if (segment[0] >= location.column) {
if (segment.length === 1) break;
if (line) {
const filteredLine = line.filter(
(segment): segment is [number, number, number, number] => segment.length > 1
);
const lastSegment = filteredLine[filteredLine.length - 1];
for (const segment of filteredLine) {
if (segment[0] >= location.column || segment === lastSegment) {
location = {
column: segment[3],
line: segment[2] + 1,
name: segment.length === 5 ? sourcemap.names[segment[4]] : undefined,
source: sourcemap.sources[segment[1]]
line: segment[2] + 1
};
locationFound = true;
break;
continue traceSourcemap;
}
}
}
if (!locationFound) {
throw new Error("Can't resolve original location of error.");
}
throw new Error("Can't resolve original location of error.");
}
return location;
}
Expand Up @@ -7,7 +7,7 @@ module.exports = {
plugins: {
name: 'test-plugin',
transform() {
return { code: 'export default this', map: { mappings: 'X' } };
return { code: 'export default this', map: { mappings: '' } };
}
}
},
Expand Down
37 changes: 37 additions & 0 deletions test/function/samples/warning-low-resolution-location/_config.js
@@ -0,0 +1,37 @@
const path = require('path');
const { encode } = require('sourcemap-codec');
const ID_MAIN = path.join(__dirname, 'main.js');

module.exports = {
description: 'handles when a low resolution sourcemap is used to report an error',
options: {
plugins: {
name: 'test-plugin',
transform() {
// each entry of each line consist of
// [generatedColumn, sourceIndex, sourceLine, sourceColumn];
// this mapping only maps the first line to itself
const decodedMap = [[[0], [0, 0, 0, 0], [1]]];
return { code: 'export default this', map: { mappings: encode(decodedMap), sources: [] } };
}
}
},
warnings: [
{
code: 'THIS_IS_UNDEFINED',
frame: `
1: console.log('original source');
^`,
id: ID_MAIN,
loc: {
column: 0,
file: ID_MAIN,
line: 1
},
message:
"The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten",
pos: 15,
url: 'https://rollupjs.org/guide/en/#error-this-is-undefined'
}
]
};
@@ -0,0 +1 @@
console.log('original source');
47 changes: 47 additions & 0 deletions test/sourcemaps/samples/transform-low-resolution/_config.js
@@ -0,0 +1,47 @@
const assert = require('assert');
const MagicString = require('magic-string');
const { SourceMapConsumer } = require('source-map');
const { encode } = require('sourcemap-codec');
const getLocation = require('../../getLocation');

module.exports = {
description: 'handles combining low-resolution and high-resolution source-maps when transforming',
options: {
output: { name: 'bundle' },
plugins: [
{
transform(code) {
// each entry of each line consist of
// [generatedColumn, sourceIndex, sourceLine, sourceColumn];
// this mapping only maps the second line to the first with no column
// details
const decodedMap = [[], [[0, 0, 0, 0]]];
return {
code: `console.log('added');\n${code}`,
map: { mappings: encode(decodedMap) }
};
}
},
{
transform(code) {
const s = new MagicString(code);
s.prepend("console.log('second');\n");

return {
code: s.toString(),
map: s.generateMap({ hires: true })
};
}
}
]
},
async test(code, map) {
const smc = await new SourceMapConsumer(map);

const generatedLoc = getLocation(code, code.indexOf("'baz'"));
const originalLoc = smc.originalPositionFor(generatedLoc);

assert.strictEqual(originalLoc.line, 1);
assert.strictEqual(originalLoc.column, 0);
}
};
1 change: 1 addition & 0 deletions test/sourcemaps/samples/transform-low-resolution/main.js
@@ -0,0 +1 @@
export let foo = 'bar'; foo += 'baz';

0 comments on commit 46dc17f

Please sign in to comment.