Skip to content

Commit

Permalink
Fix map resolution
Browse files Browse the repository at this point in the history
You prefix `source` with `sourceRoot`, then resolve it relative to the map URL. Before we incorrectly allowed a map's source to be absolute after the `sourceRoot` was specified.

This matches `source-map` and Chrome's behaviors.
  • Loading branch information
jridgewell committed Mar 4, 2024
1 parent f8a42eb commit ff27459
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 23 deletions.
14 changes: 10 additions & 4 deletions src/resolve.ts
@@ -1,10 +1,16 @@
import resolveUri from '@jridgewell/resolve-uri';
import stripFilename from './strip-filename';

export default function resolve(input: string, base: string | undefined): string {
// The base is always treated as a directory, if it's not empty.
type Resolve = (source: string | null) => string;
export default function resolver(
mapUrl: string | null | undefined,
sourceRoot: string | undefined,
): Resolve {
const from = stripFilename(mapUrl);
// The sourceRoot is always treated as a directory, if it's not empty.
// https://github.com/mozilla/source-map/blob/8cb3ee57/lib/util.js#L327
// https://github.com/chromium/chromium/blob/da4adbb3/third_party/blink/renderer/devtools/front_end/sdk/SourceMap.js#L400-L401
if (base && !base.endsWith('/')) base += '/';
const prefix = sourceRoot ? sourceRoot + '/' : '';

return resolveUri(input, base);
return (source) => resolveUri(prefix + (source || ''), from);
}
7 changes: 3 additions & 4 deletions src/trace-mapping.ts
@@ -1,7 +1,6 @@
import { encode, decode } from '@jridgewell/sourcemap-codec';

import resolve from './resolve';
import stripFilename from './strip-filename';
import resolver from './resolve';
import maybeSort from './sort';
import buildBySources from './by-source';
import {
Expand Down Expand Up @@ -119,8 +118,8 @@ export class TraceMap implements SourceMap {
this.sourcesContent = sourcesContent;
this.ignoreList = parsed.ignoreList || (parsed as XInput).x_google_ignoreList || undefined;

const from = resolve(sourceRoot || '', stripFilename(mapUrl));
this.resolvedSources = sources.map((s) => resolve(s || '', from));
const resolve = resolver(mapUrl, sourceRoot);
this.resolvedSources = sources.map((s) => resolve(s));

const { mappings } = parsed;
if (typeof mappings === 'string') {
Expand Down
5 changes: 1 addition & 4 deletions src/types.ts
Expand Up @@ -20,10 +20,7 @@ export interface DecodedSourceMap extends SourceMapV3 {
}

export interface Section {
offset: {
line: number;
column: number;
};
offset: { line: number; column: number };
map: EncodedSourceMap | DecodedSourceMap | SectionedSourceMap;
}

Expand Down
34 changes: 25 additions & 9 deletions test/resolve.test.ts
@@ -1,18 +1,34 @@
import { strict as assert } from 'assert';
import resolve from '../src/resolve';
import resolver from '../src/resolve';

describe('resolve', () => {
it('resolves input relative to base', () => {
const base = 'bar/';
const input = 'foo';
it('unresolved without sourceRoot', () => {
const resolve = resolver(undefined, undefined);
assert.equal(resolve('input.js'), 'input.js');
});

it('relative to mapUrl', () => {
const resolve = resolver('foo/script.js.map', undefined);
assert.equal(resolve('input.js'), 'foo/input.js');
});

assert.equal(resolve(input, base), 'bar/foo');
it('relative to sourceRoot', () => {
const resolve = resolver(undefined, 'foo');
assert.equal(resolve('input.js'), 'foo/input.js');
});

it('treats base as a directory regardless of slash', () => {
const base = 'bar';
const input = 'foo';
it('relative to mapUrl then sourceRoot', () => {
const resolve = resolver('foo/script.js.map', 'bar');
assert.equal(resolve('input.js'), 'foo/bar/input.js');
});

it('prepends sourceRoot to source before resolving', () => {
const resolve = resolver('foo/script.js.map', 'bar');
assert.equal(resolve('/input.js'), 'foo/bar/input.js');
});

assert.equal(resolve(input, base), 'bar/foo');
it('skips undefined sourceRoot before resolving', () => {
const resolve = resolver('foo/script.js.map', undefined);
assert.equal(resolve('/input.js'), '/input.js');
});
});
3 changes: 1 addition & 2 deletions test/trace-mapping.test.ts
Expand Up @@ -31,8 +31,7 @@ describe('TraceMap', () => {
const decodedMap: DecodedSourceMap = {
version: 3,
sources: ['input.js'],
sourceRoot:
'https://astexplorer.net/#/gist/d91f04e37e8e12eec06f2886e6bc3a4d/56cd06cd895d3b638b4100658b0027787ca5e5f1',
sourceRoot: 'https://astexplorer.net/',
names: ['foo', 'bar', 'Error'],
mappings: [
[
Expand Down

0 comments on commit ff27459

Please sign in to comment.