chrome-devtools-frontend
Version:
Chrome DevTools UI
1,195 lines (1,069 loc) • 57.5 kB
text/typescript
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as TextUtils from '../../models/text_utils/text_utils.js';
import {describeWithEnvironment} from '../../testing/EnvironmentHelpers.js';
import {encodeSourceMap, GeneratedRangeBuilder, OriginalScopeBuilder} from '../../testing/SourceMapEncoder.js';
import * as Platform from '../platform/platform.js';
import * as Root from '../root/root.js';
import * as SDK from './sdk.js';
const {urlString} = Platform.DevToolsPath;
const sourceUrlFoo = urlString`<foo>`;
describe('SourceMapEntry', () => {
it('can be instantiated correctly', () => {
const sourceMapEntry =
new SDK.SourceMap.SourceMapEntry(1, 1, 0, urlString`http://www.example.com/`, 1, 1, 'example');
assert.strictEqual(sourceMapEntry.lineNumber, 1, 'line number was not set correctly');
assert.strictEqual(sourceMapEntry.columnNumber, 1, 'column number was not set correctly');
assert.strictEqual(
sourceMapEntry.sourceURL, urlString`http://www.example.com/`, 'source URL was not set correctly');
assert.strictEqual(sourceMapEntry.sourceLineNumber, 1, 'source line number was not set correctly');
assert.strictEqual(sourceMapEntry.sourceColumnNumber, 1, 'source column number was not set correctly');
assert.strictEqual(sourceMapEntry.name, 'example', 'name was not set correctly');
});
describe('comparison', () => {
it('checks line numbers first', () => {
const sourceMapEntry1 = new SDK.SourceMap.SourceMapEntry(1, 5, 0, sourceUrlFoo, 1, 5, 'foo');
const sourceMapEntry2 = new SDK.SourceMap.SourceMapEntry(2, 5, 0, sourceUrlFoo, 2, 5, 'foo');
assert.isBelow(
SDK.SourceMap.SourceMapEntry.compare(sourceMapEntry1, sourceMapEntry2), 0, 'first entry is not smaller');
});
it('checks column numbers second when line numbers are equal', () => {
const sourceMapEntry1 = new SDK.SourceMap.SourceMapEntry(2, 5, 0, sourceUrlFoo, 1, 5, 'foo');
const sourceMapEntry2 = new SDK.SourceMap.SourceMapEntry(2, 25, 0, sourceUrlFoo, 2, 5, 'foo');
assert.isBelow(
SDK.SourceMap.SourceMapEntry.compare(sourceMapEntry1, sourceMapEntry2), 0, 'first entry is not smaller');
});
it('works for equal SourceMapEntries', () => {
const sourceMapEntry1 = new SDK.SourceMap.SourceMapEntry(2, 5, 0, sourceUrlFoo, 1, 5, 'foo');
const sourceMapEntry2 = new SDK.SourceMap.SourceMapEntry(2, 5, 0, sourceUrlFoo, 1, 5, 'foo');
assert.strictEqual(SDK.SourceMap.SourceMapEntry.compare(sourceMapEntry1, sourceMapEntry2), 0);
});
});
});
describeWithEnvironment('SourceMap', () => {
const compiledUrl = urlString`compiled.js`;
const sourceMapJsonUrl = urlString`source-map.json`;
const sourceUrlExample = urlString`example.js`;
const sourceUrlOther = urlString`other.js`;
describe('TokenIterator', () => {
it('detects when it has reached the end', () => {
const emptyIterator = new SDK.SourceMap.TokenIterator('');
assert.isFalse(emptyIterator.hasNext());
const iterator = new SDK.SourceMap.TokenIterator('foo');
assert.isTrue(iterator.hasNext());
});
it('peeks the next character', () => {
const emptyIterator = new SDK.SourceMap.TokenIterator('');
assert.strictEqual(emptyIterator.peek(), '');
const iterator = new SDK.SourceMap.TokenIterator('foo');
assert.strictEqual(iterator.peek(), 'f');
});
it('advances when {next} is called', () => {
const iterator = new SDK.SourceMap.TokenIterator('bar');
assert.strictEqual(iterator.next(), 'b');
assert.strictEqual(iterator.next(), 'a');
assert.strictEqual(iterator.next(), 'r');
assert.isFalse(iterator.hasNext());
});
it('peeks the next VLQ number without moving the iterator', () => {
const cases: [string, number|null][] = [
['', null],
['0C', 42],
['0C0C', 42],
[',0C', null],
[';0C', null],
['$foo', null],
];
for (const [input, expectedOutput] of cases) {
const iter = new SDK.SourceMap.TokenIterator(input);
// Call twice to make sure the iterator doesn't move in all cases.
assert.strictEqual(iter.peekVLQ(), expectedOutput, `'${input}' must result in '${expectedOutput}'`);
assert.strictEqual(iter.peekVLQ(), expectedOutput, `'${input}' must result in '${expectedOutput}'`);
}
});
});
function assertMapping(
actual: SDK.SourceMap.SourceMapEntry|null, expectedSourceIndex: number|undefined,
expectedSourceURL: string|undefined, expectedSourceLineNumber: number|undefined,
expectedSourceColumnNumber: number|undefined) {
assert.exists(actual);
assert.strictEqual(actual.sourceIndex, expectedSourceIndex, 'unexpected source index');
assert.strictEqual(actual.sourceURL, expectedSourceURL, 'unexpected source URL');
assert.strictEqual(actual.sourceLineNumber, expectedSourceLineNumber, 'unexpected source line number');
assert.strictEqual(actual.sourceColumnNumber, expectedSourceColumnNumber, 'unexpected source column number');
}
function assertReverseMapping(
actual: SDK.SourceMap.SourceMapEntry|null, expectedCompiledLineNumber: number,
expectedCompiledColumnNumber: number) {
assert.exists(actual);
assert.strictEqual(actual.lineNumber, expectedCompiledLineNumber, 'unexpected compiled line number');
assert.strictEqual(actual.columnNumber, expectedCompiledColumnNumber, 'unexpected compiled column number');
}
// FIXME(szuend): The following tests are a straight-up port from a corresponding layout test.
// These tests should be cleaned up, made more readable and maybe refactor
// the underlying code to make the individual parts more testable.
it('can parse a simple source map', () => {
/*
The numbers above the respective scripts are column numbers from 0 to 35.
example.js:
0 1 2 3
012345678901234567890123456789012345
function add(variable_x, variable_y)
{
return variable_x + variable_y;
}
var global = "foo";
----------------------------------------
example-compiled.js:
0 1 2 3
012345678901234567890123456789012345
function add(a,b){return a+b}var global="foo";
foo
*/
const mappingPayload = encodeSourceMap([
// clang-format off
'0:0 => example.js:0:9@add',
'0:8 => example.js:0:9@add',
'0:12 => example.js:0:12',
'0:13 => example.js:0:13@variable_x',
'0:14 => example.js:0:12',
'0:15 => example.js:0:25@variable_y',
'0:16 => example.js:0:12',
'0:17 => example.js:1:0',
'0:18 => example.js:2:4',
'0:24 => example.js:2:11@variable_x',
'0:26 => example.js:2:4',
'0:27 => example.js:2:24@variable_y',
'0:28 => example.js:1:0',
'0:29 => example.js:5:0',
'0:33 => example.js:5:4@global',
'0:40 => example.js:5:13',
'1:0',
// clang-format on
]);
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
assertMapping(sourceMap.findEntry(0, 9), 0, 'example.js', 0, 9);
assertMapping(sourceMap.findEntry(0, 13), 0, 'example.js', 0, 13);
assertMapping(sourceMap.findEntry(0, 15), 0, 'example.js', 0, 25);
assertMapping(sourceMap.findEntry(0, 18), 0, 'example.js', 2, 4);
assertMapping(sourceMap.findEntry(0, 25), 0, 'example.js', 2, 11);
assertMapping(sourceMap.findEntry(0, 27), 0, 'example.js', 2, 24);
assertMapping(sourceMap.findEntry(1, 0), undefined, undefined, undefined, undefined);
assertReverseMapping(sourceMap.sourceLineMapping(sourceUrlExample, 0, 0), 0, 0);
assertReverseMapping(sourceMap.sourceLineMapping(sourceUrlExample, 1, 0), 0, 17);
assertReverseMapping(sourceMap.sourceLineMapping(sourceUrlExample, 2, 0), 0, 18);
assert.isNull(sourceMap.sourceLineMapping(sourceUrlExample, 4, 0), 'unexpected source mapping for line 4');
assertReverseMapping(sourceMap.sourceLineMapping(sourceUrlExample, 5, 0), 0, 29);
});
it('can do reverse lookups', () => {
const mappingPayload = encodeSourceMap([
// clang-format off
'0:0 => example.js:1:0',
'1:0 => example.js:3:0',
'2:0 => example.js:1:0',
'4:0 => other.js:5:0',
'5:0 => example.js:3:0',
'7:2 => example.js:1:0',
'10:5 => other.js:5:0',
// clang-format on
]);
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
// Exact match for source location.
assert.deepEqual(sourceMap.findReverseRanges(sourceUrlExample, 3, 0).map(r => r.serializeToObject()), [
{startLine: 1, startColumn: 0, endLine: 2, endColumn: 0},
{startLine: 5, startColumn: 0, endLine: 7, endColumn: 2},
]);
// Inexact match.
assert.deepEqual(sourceMap.findReverseRanges(sourceUrlExample, 10, 0).map(r => r.serializeToObject()), [
{startLine: 1, startColumn: 0, endLine: 2, endColumn: 0},
{startLine: 5, startColumn: 0, endLine: 7, endColumn: 2},
]);
// Match with more than two locations.
assert.deepEqual(sourceMap.findReverseRanges(sourceUrlExample, 1, 0).map(r => r.serializeToObject()), [
{startLine: 0, startColumn: 0, endLine: 1, endColumn: 0},
{startLine: 2, startColumn: 0, endLine: 4, endColumn: 0},
{startLine: 7, startColumn: 2, endLine: 10, endColumn: 5},
]);
// Match at the end of file.
assert.deepEqual(sourceMap.findReverseRanges(sourceUrlOther, 5, 0).map(r => r.serializeToObject()), [
{startLine: 4, startColumn: 0, endLine: 5, endColumn: 0},
{startLine: 10, startColumn: 5, endLine: 2 ** 31 - 1, endColumn: 2 ** 31 - 1},
]);
// No match.
assert.isEmpty(sourceMap.findReverseRanges(sourceUrlExample, 0, 0));
assert.isEmpty(sourceMap.findReverseRanges(sourceUrlOther, 1, 0));
// Also test the reverse lookup that returns points.
assert.deepEqual(sourceMap.findReverseEntries(sourceUrlOther, 5, 0).map(e => e.lineNumber), [4, 10]);
assert.deepEqual(sourceMap.findReverseEntries(sourceUrlOther, 10, 0).map(e => e.lineNumber), [4, 10]);
});
it('can do reverse lookups with merging', () => {
const mappingPayload = encodeSourceMap([
// clang-format off
'0:0 => example.js:1:0',
'1:0 => example.js:3:0',
'2:0 => example.js:1:0',
'3:0 => example.js:1:0',
'4:0 => example.js:1:0',
'5:0 => example.js:2:0',
'5:2 => example.js:2:1',
'5:4 => example.js:2:1',
'5:6 => example.js:2:2',
'5:8 => example.js:2:1',
'6:2 => example.js:2:1',
'6:4 => example.js:2:2',
'7:0 => example.js:1:0',
'8:0 => example.js:1:0',
// clang-format on
]);
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
assert.deepEqual(sourceMap.findReverseRanges(sourceUrlExample, 1, 0).map(r => r.serializeToObject()), [
{startLine: 0, startColumn: 0, endLine: 1, endColumn: 0},
{startLine: 2, startColumn: 0, endLine: 5, endColumn: 0},
{startLine: 7, startColumn: 0, endLine: 2 ** 31 - 1, endColumn: 2 ** 31 - 1},
]);
assert.deepEqual(sourceMap.findReverseRanges(sourceUrlExample, 2, 1).map(r => r.serializeToObject()), [
{startLine: 5, startColumn: 2, endLine: 5, endColumn: 6},
{startLine: 5, startColumn: 8, endLine: 6, endColumn: 4},
]);
});
it('can parse source maps with segments that contain no mapping information', () => {
const mappingPayload = {
mappings: 'AAAA,C,CAAE;',
sources: [sourceUrlExample],
version: 3,
};
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
assertMapping(sourceMap.findEntry(0, 0), 0, 'example.js', 0, 0);
assertMapping(sourceMap.findEntry(0, 2), 0, 'example.js', 0, 2);
const emptyEntry = sourceMap.findEntry(0, 1);
assert.exists(emptyEntry);
assert.isUndefined(emptyEntry.sourceURL, 'unexpected url present for empty segment');
assert.isUndefined(emptyEntry.sourceLineNumber, 'unexpected source line number for empty segment');
assert.isUndefined(emptyEntry.sourceColumnNumber, 'unexpected source line number for empty segment');
});
it('can parse source maps with empty lines', () => {
const mappingPayload = {
mappings: 'AAAA;;;CACA',
sources: [sourceUrlExample],
version: 3,
};
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
assertMapping(sourceMap.findEntry(0, 0), 0, 'example.js', 0, 0);
assertReverseMapping(sourceMap.sourceLineMapping(sourceUrlExample, 1, 0), 3, 1);
});
it('can parse source maps with mappings in reverse direction', () => {
/*
example.js:
ABCD
compiled.js:
DCBA
*/
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, {
// mappings go in reversed direction.
mappings: 'GAAA,DAAC,DAAC,DAAC',
sources: ['example.js'],
version: 3,
});
assertMapping(sourceMap.findEntry(0, 0), 0, 'example.js', 0, 3);
assertMapping(sourceMap.findEntry(0, 1), 0, 'example.js', 0, 2);
assertMapping(sourceMap.findEntry(0, 2), 0, 'example.js', 0, 1);
assertMapping(sourceMap.findEntry(0, 3), 0, 'example.js', 0, 0);
});
it('can parse the multiple sections format', () => {
const mappingPayload: SDK.SourceMap.SourceMapV3 = {
sections: [
{
offset: {line: 0, column: 0},
map: {
mappings: 'AAAA,CAEC',
sources: ['source1.js', 'source2.js'],
version: 3,
},
},
{
offset: {line: 2, column: 10},
map: {
mappings: 'AAAA,CAEC',
sources: ['source3.js'],
version: 3,
},
},
],
version: 3,
};
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
assert.lengthOf(sourceMap.sourceURLs(), 3, 'unexpected number of original source URLs');
assertMapping(sourceMap.findEntry(0, 0), 0, 'source1.js', 0, 0);
assertMapping(sourceMap.findEntry(0, 1), 0, 'source1.js', 2, 1);
assertMapping(sourceMap.findEntry(2, 10), 2, 'source3.js', 0, 0);
assertMapping(sourceMap.findEntry(2, 11), 2, 'source3.js', 2, 1);
});
it('can parse source maps with ClosureScript names', () => {
/*
------------------------------------------------------------------------------------
chrome_issue_611738.clj:
(ns devtools-sample.chrome-issue-611738)
(defmacro m []
`(let [generated# "value2"]))
------------------------------------------------------------------------------------
chrome_issue_611738.cljs:
(ns devtools-sample.chrome-issue-611738
(:require-macros [devtools-sample.chrome-issue-611738 :refer [m]]))
(let [name1 "value1"]
(m))
------------------------------------------------------------------------------------
chrome_issue_611738.js:
// Compiled by ClojureScript 1.9.89 {}
goog.provide('devtools_sample.chrome_issue_611738');
goog.require('cljs.core');
var name1_31466 = "value1";
var generated31465_31467 = "value2";
//# sourceMappingURL=chrome_issue_611738.js.map
------------------------------------------------------------------------------------
chrome_issue_611738.js.map:
{"version":3,"file":"\/Users\/darwin\/code\/cljs-devtools-sample\/resources\/public\/_compiled\/demo\/devtools_sample\/chrome_issue_611738.js","sources":["chrome_issue_611738.cljs"],"lineCount":7,"mappings":";AAAA;;AAGA,kBAAA,dAAMA;AAAN,AACE,IAAAC,uBAAA;AAAA,AAAA","names":["name1","generated31465"]}
------------------------------------------------------------------------------------
*/
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, {
version: 3,
sources: ['chrome_issue_611738.cljs'],
mappings: ';AAAA;;AAGA,kBAAA,dAAMA;AAAN,AACE,IAAAC,uBAAA;AAAA,AAAA',
names: ['name1', 'generated31465'],
});
assert.propertyVal(sourceMap.findEntry(1, 0), 'name', undefined);
assert.propertyVal(sourceMap.findEntry(3, 0), 'name', undefined);
assert.propertyVal(sourceMap.findEntry(3, 4), 'name', 'name1');
assert.propertyVal(sourceMap.findEntry(3, 18), 'name', undefined);
assert.propertyVal(sourceMap.findEntry(4, 0), 'name', undefined);
assert.propertyVal(sourceMap.findEntry(4, 4), 'name', 'generated31465');
assert.propertyVal(sourceMap.findEntry(4, 27), 'name', undefined);
assert.propertyVal(sourceMap.findEntry(5, 0), 'name', undefined);
});
it('resolves duplicate canonical urls', () => {
const mappingPayload = encodeSourceMap(
[
// clang-format off
'0:0 => example.js:1:0',
'1:0 => ./example.js:3:0',
'2:0 => example.js:1:0',
'4:0 => other.js:5:0',
'5:0 => example.js:3:0',
'7:2 => example.js:1:0',
'10:5 => other.js:5:0',
// clang-format on
],
'wp:///' /* sourceRoot */);
const sourceMapJsonUrl = urlString`wp://test/source-map.json`;
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
assertMapping(sourceMap.findEntry(0, 0), 0, 'wp:///example.js', 1, 0);
assertMapping(sourceMap.findEntry(1, 0), 1, 'wp:///example.js', 3, 0);
assertMapping(sourceMap.findEntry(4, 0), 2, 'wp:///other.js', 5, 0);
});
describe('compatibleForURL', () => {
const compiledURL = urlString`http://example.com/foo.js`;
const sourceMappingURL = urlString`${`${compiledURL}.map`}`;
const sourceRoot = 'webpack:///src';
const sourceURL = urlString`${`${sourceRoot}/foo.ts`}`;
it('correctly identifies equal sourcemaps with content', () => {
const payload = {
mappings: '',
sourceRoot,
sources: ['foo.ts'],
sourcesContent: ['function foo() {\n console.log("Hello world!");\n}'],
version: 3,
};
const sourceMap1 = new SDK.SourceMap.SourceMap(compiledURL, sourceMappingURL, payload);
const sourceMap2 = new SDK.SourceMap.SourceMap(compiledURL, sourceMappingURL, payload);
assert.isTrue(sourceMap1.compatibleForURL(sourceURL, sourceMap2));
assert.isTrue(sourceMap2.compatibleForURL(sourceURL, sourceMap1));
});
it('correctly identifies equal sourcemaps without content', () => {
const payload = {
mappings: '',
sourceRoot,
sources: ['foo.ts'],
version: 3,
};
const sourceMap1 = new SDK.SourceMap.SourceMap(compiledURL, sourceMappingURL, payload);
const sourceMap2 = new SDK.SourceMap.SourceMap(compiledURL, sourceMappingURL, payload);
assert.isTrue(sourceMap1.compatibleForURL(sourceURL, sourceMap2));
assert.isTrue(sourceMap2.compatibleForURL(sourceURL, sourceMap1));
});
it('correctly differentiates based on content', () => {
const sourceMap1 = new SDK.SourceMap.SourceMap(compiledURL, sourceMappingURL, {
mappings: '',
sourceRoot,
sources: ['foo.ts'],
sourcesContent: ['function foo() {\n console.log("Hello from first!");\n}'],
version: 3,
});
const sourceMap2 = new SDK.SourceMap.SourceMap(compiledURL, sourceMappingURL, {
mappings: '',
sourceRoot,
sources: ['foo.ts'],
sourcesContent: ['function foo() {\n console.log("Hello from second!");\n}'],
version: 3,
});
assert.isFalse(sourceMap1.compatibleForURL(sourceURL, sourceMap2));
assert.isFalse(sourceMap2.compatibleForURL(sourceURL, sourceMap1));
});
it('correctly differentiates based on ignore-list hint', () => {
const payload1 = {
mappings: '',
sourceRoot,
sources: ['foo.ts'],
sourcesContent: ['function foo() {\n console.log("Hello world!");\n}'],
version: 3,
};
const payload2 = {
...payload1,
ignoreList: [0],
};
const sourceMap1 = new SDK.SourceMap.SourceMap(compiledURL, sourceMappingURL, payload1);
const sourceMap2 = new SDK.SourceMap.SourceMap(compiledURL, sourceMappingURL, payload2);
assert.isFalse(sourceMap1.compatibleForURL(sourceURL, sourceMap2));
assert.isFalse(sourceMap2.compatibleForURL(sourceURL, sourceMap1));
});
});
describe('source URL resolution', () => {
const noSourceRoot = '';
const absoluteSourceRootExample = 'http://example.com/src';
const absoluteSourceRootFoo = 'http://foo.com/src';
const relativeSourceRootSrc = 'src';
const relativeSourceRootSlashSrc = '/src';
const relativeSourceRootSrcSlash = 'src/';
const relativeSourceRootCSlashD = 'c/d';
const cases = [
// No sourceRoot, relative sourceURL. sourceURL is normalized and resolved relative to sourceMapURL.
{
sourceRoot: noSourceRoot,
sourceURL: 'foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'http://example.com/foo.ts',
},
{
sourceRoot: noSourceRoot,
sourceURL: 'foo.ts',
sourceMapURL: 'http://example.com/a/foo.js.map',
expected: 'http://example.com/a/foo.ts',
},
{
sourceRoot: noSourceRoot,
sourceURL: 'foo.ts',
sourceMapURL: 'http://example.com/a/b/foo.js.map',
expected: 'http://example.com/a/b/foo.ts',
},
{
sourceRoot: noSourceRoot,
sourceURL: '/foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'http://example.com/foo.ts',
},
{
sourceRoot: noSourceRoot,
sourceURL: '/foo.ts',
sourceMapURL: 'http://example.com/a/foo.js.map',
expected: 'http://example.com/foo.ts',
},
{
sourceRoot: noSourceRoot,
sourceURL: '/foo.ts',
sourceMapURL: 'http://example.com/a/b/foo.js.map',
expected: 'http://example.com/foo.ts',
},
{
sourceRoot: noSourceRoot,
sourceURL: '/./foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'http://example.com/foo.ts',
},
{
sourceRoot: noSourceRoot,
sourceURL: '/./foo.ts',
sourceMapURL: 'http://example.com/a/foo.js.map',
expected: 'http://example.com/foo.ts',
},
{
sourceRoot: noSourceRoot,
sourceURL: '/./foo.ts',
sourceMapURL: 'http://example.com/a/b/foo.js.map',
expected: 'http://example.com/foo.ts',
},
{
sourceRoot: noSourceRoot,
sourceURL: '../foo.ts',
sourceMapURL: 'http://example.com/a/b/foo.js.map',
expected: 'http://example.com/a/foo.ts',
},
{
sourceRoot: noSourceRoot,
sourceURL: '../../foo.ts',
sourceMapURL: 'http://example.com/a/b/foo.js.map',
expected: 'http://example.com/foo.ts',
},
{
sourceRoot: noSourceRoot,
sourceURL: '../../../foo.ts',
sourceMapURL: 'http://example.com/a/b/foo.js.map',
expected: 'http://example.com/foo.ts',
},
// No sourceRoot, absolute sourceURL. The sourceURL is normalized and then used as-is.
{
sourceRoot: noSourceRoot,
sourceURL: 'webpack://example/src/foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'webpack://example/src/foo.ts',
},
{
sourceRoot: noSourceRoot,
sourceURL: 'webpack://example/src/a/b/foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'webpack://example/src/a/b/foo.ts',
},
{
sourceRoot: noSourceRoot,
sourceURL: 'webpack://example/../../../src/a/b/foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'webpack://example/src/a/b/foo.ts',
},
{
sourceRoot: noSourceRoot,
sourceURL: 'webpack://example/src/a/../b/foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'webpack://example/src/b/foo.ts',
},
// Relative sourceRoot, relative sourceURL. The sourceRoot and sourceURL paths are concatenated and normalized before resolving against the sourceMapURL.
{
sourceRoot: relativeSourceRootSrc,
sourceURL: 'foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'http://example.com/src/foo.ts',
},
{
sourceRoot: relativeSourceRootSrc,
sourceURL: 'foo.ts',
sourceMapURL: 'http://example.com/a/foo.js.map',
expected: 'http://example.com/a/src/foo.ts',
},
{
sourceRoot: relativeSourceRootSrc,
sourceURL: 'foo.ts',
sourceMapURL: 'http://example.com/a/b/foo.js.map',
expected: 'http://example.com/a/b/src/foo.ts',
},
{
sourceRoot: relativeSourceRootSrc,
sourceURL: '/foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'http://example.com/src/foo.ts',
},
{
sourceRoot: relativeSourceRootSrc,
sourceURL: '/foo.ts',
sourceMapURL: 'http://example.com/a/foo.js.map',
expected: 'http://example.com/a/src/foo.ts',
},
{
sourceRoot: relativeSourceRootSrc,
sourceURL: '/foo.ts',
sourceMapURL: 'http://example.com/a/b/foo.js.map',
expected: 'http://example.com/a/b/src/foo.ts',
},
{
sourceRoot: relativeSourceRootSrcSlash,
sourceURL: 'foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'http://example.com/src/foo.ts',
},
{
sourceRoot: relativeSourceRootSrcSlash,
sourceURL: 'foo.ts',
sourceMapURL: 'http://example.com/a/foo.js.map',
expected: 'http://example.com/a/src/foo.ts',
},
{
sourceRoot: relativeSourceRootSrcSlash,
sourceURL: 'foo.ts',
sourceMapURL: 'http://example.com/a/b/foo.js.map',
expected: 'http://example.com/a/b/src/foo.ts',
},
{
sourceRoot: relativeSourceRootSlashSrc,
sourceURL: 'foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'http://example.com/src/foo.ts',
},
{
sourceRoot: relativeSourceRootSlashSrc,
sourceURL: 'foo.ts',
sourceMapURL: 'http://example.com/a/foo.js.map',
expected: 'http://example.com/src/foo.ts',
},
{
sourceRoot: relativeSourceRootSlashSrc,
sourceURL: 'foo.ts',
sourceMapURL: 'http://example.com/a/b/foo.js.map',
expected: 'http://example.com/src/foo.ts',
},
{
sourceRoot: relativeSourceRootSrc,
sourceURL: '../foo.ts',
sourceMapURL: 'http://example.com/a/b/foo.js.map',
expected: 'http://example.com/a/b/foo.ts',
},
{
sourceRoot: relativeSourceRootSrc,
sourceURL: '../../foo.ts',
sourceMapURL: 'http://example.com/a/b/foo.js.map',
expected: 'http://example.com/a/foo.ts',
},
{
sourceRoot: relativeSourceRootSrc,
sourceURL: '../../../foo.ts',
sourceMapURL: 'http://example.com/a/foo.js.map',
expected: 'http://example.com/foo.ts',
},
// Relative sourceRoot, absolute sourceURL. Ignore the sourceRoot, normalize the sourceURL.
{
sourceRoot: relativeSourceRootCSlashD,
sourceURL: 'webpack://example/src/foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'webpack://example/src/foo.ts',
},
{
sourceRoot: relativeSourceRootCSlashD,
sourceURL: 'webpack://example/../../../src/a/b/foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'webpack://example/src/a/b/foo.ts',
},
// Absolute sourceRoot, relative sourceURL. Append the sourceURL path into the sourceRoot path, normalize and use the resulting URL.
{
sourceRoot: absoluteSourceRootExample,
sourceURL: 'foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'http://example.com/src/foo.ts',
},
{
sourceRoot: absoluteSourceRootExample,
sourceURL: 'a/foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'http://example.com/src/a/foo.ts',
},
{
sourceRoot: absoluteSourceRootExample,
sourceURL: 'a/b/foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'http://example.com/src/a/b/foo.ts',
},
{
sourceRoot: absoluteSourceRootExample,
sourceURL: '/foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'http://example.com/src/foo.ts',
},
{
sourceRoot: absoluteSourceRootExample,
sourceURL: '/a/foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'http://example.com/src/a/foo.ts',
},
{
sourceRoot: absoluteSourceRootExample,
sourceURL: '/a/b/foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'http://example.com/src/a/b/foo.ts',
},
{
sourceRoot: absoluteSourceRootExample,
sourceURL: '../foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'http://example.com/foo.ts',
},
{
sourceRoot: absoluteSourceRootExample,
sourceURL: '../../foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'http://example.com/foo.ts',
},
{
sourceRoot: 'http://example.com/src/a/b',
sourceURL: '../../../foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'http://example.com/foo.ts',
},
// Absolute sourceRoot, absolute sourceURL. Ignore the sourceRoot, normalize the sourceURL.
{
sourceRoot: absoluteSourceRootFoo,
sourceURL: 'webpack://example/src/foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'webpack://example/src/foo.ts',
},
{
sourceRoot: absoluteSourceRootFoo,
sourceURL: 'webpack://example/../../../src/a/b/foo.ts',
sourceMapURL: 'http://example.com/foo.js.map',
expected: 'webpack://example/src/a/b/foo.ts',
},
{
sourceRoot: noSourceRoot,
sourceURL: 'file.ts',
sourceMapURL: 'https://example.com/some///random/file.js.map',
expected: 'https://example.com/some///random/file.ts',
},
{
sourceRoot: noSourceRoot,
sourceURL: 'https://example.com/some///random/file.ts',
sourceMapURL: 'https://example.com/some///random/file.js.map',
expected: 'https://example.com/some///random/file.ts',
},
];
for (const {sourceRoot, sourceURL, sourceMapURL, expected} of cases) {
it(`can resolve sourceURL "${sourceURL}" with sourceRoot "${sourceRoot}" and sourceMapURL "${sourceMapURL}"`,
() => {
const mappingPayload = {mappings: 'AAAA;;;CACA', sourceRoot, sources: [sourceURL], version: 3};
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, urlString`${sourceMapURL}`, mappingPayload);
const sourceURLs = sourceMap.sourceURLs();
assert.lengthOf(sourceURLs, 1, 'unexpected number of original source URLs');
assert.strictEqual(sourceURLs[0], expected);
});
}
it('does not touch sourceURLs that conflict with the compiled URL', () => {
const sourceURL = urlString`http://localhost:12345/index.js`;
const sourceMappingURL = urlString`${`${sourceURL}.map`}`;
const sourceMap = new SDK.SourceMap.SourceMap(sourceURL, sourceMappingURL, {
version: 3,
sources: [sourceURL],
sourcesContent: ['console.log(42)'],
mappings: '',
});
const sourceURLs = sourceMap.sourceURLs();
assert.lengthOf(sourceURLs, 1);
assert.strictEqual(sourceURLs[0], sourceURL);
});
});
describe('automatic ignore-listing', () => {
it('parses the known third parties from the `ignoreList` section', () => {
const mappingPayload = encodeSourceMap(
[
// clang-format off
'0:0 => vendor.js:1:0',
'1:0 => main.js:1:0',
'2:0 => example.js:1:0',
'3:0 => other.js:1:0',
// clang-format on
],
'wp:///' /* sourceRoot */);
mappingPayload.ignoreList = [0 /* vendor.js */, 3 /* other.js */];
const sourceMapJsonUrl = urlString`wp://test/source-map.json`;
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
assert.isTrue(sourceMap.hasIgnoreListHint(urlString`wp:///vendor.js`));
assert.isFalse(sourceMap.hasIgnoreListHint(urlString`wp:///main.js`));
assert.isFalse(sourceMap.hasIgnoreListHint(urlString`wp:///example.js`));
assert.isTrue(sourceMap.hasIgnoreListHint(urlString`wp:///other.js`));
});
it('parses the known third parties from the deprecated `x_google_ignoreList` section if `ignoreList` is not present',
() => {
const mappingPayload = encodeSourceMap(
[
// clang-format off
'0:0 => vendor.js:1:0',
'1:0 => main.js:1:0',
'2:0 => example.js:1:0',
'3:0 => other.js:1:0',
// clang-format on
],
'wp:///' /* sourceRoot */);
mappingPayload.x_google_ignoreList = [0 /* vendor.js */, 3 /* other.js */];
const sourceMapJsonUrl = urlString`wp://test/source-map.json`;
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
assert.isTrue(sourceMap.hasIgnoreListHint(urlString`wp:///vendor.js`));
assert.isFalse(sourceMap.hasIgnoreListHint(urlString`wp:///main.js`));
assert.isFalse(sourceMap.hasIgnoreListHint(urlString`wp:///example.js`));
assert.isTrue(sourceMap.hasIgnoreListHint(urlString`wp:///other.js`));
});
it('parses the known third parties from the `ignoreList` section and ignores deprecated `x_google_ignoreList`',
() => {
const mappingPayload = encodeSourceMap(
[
// clang-format off
'0:0 => vendor.js:1:0',
'1:0 => main.js:1:0',
'2:0 => example.js:1:0',
'3:0 => other.js:1:0',
// clang-format on
],
'wp:///' /* sourceRoot */);
mappingPayload.ignoreList = [0 /* vendor.js */, 3 /* other.js */];
mappingPayload.x_google_ignoreList = [1 /* main.js */, 2 /* example.js */];
const sourceMapJsonUrl = urlString`wp://test/source-map.json`;
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
assert.isTrue(sourceMap.hasIgnoreListHint(urlString`wp:///vendor.js`));
assert.isFalse(sourceMap.hasIgnoreListHint(urlString`wp:///main.js`));
assert.isFalse(sourceMap.hasIgnoreListHint(urlString`wp:///example.js`));
assert.isTrue(sourceMap.hasIgnoreListHint(urlString`wp:///other.js`));
});
it('computes ranges for third party code in a simple case', () => {
const mappingPayload = encodeSourceMap(
[
// clang-format off
'0:0 => vendor1.js:1:0',
'1:0 => vendor2.js:1:0',
'2:0 => vendor3.js:1:0',
'3:0 => foo.js:1:0', // known end
// clang-format on
],
'wp:///' /* sourceRoot */);
mappingPayload.ignoreList = [0 /* vendor1.js */, 1 /* vendor2.js */, 2 /* vendor3.js */];
const sourceMapJsonUrl = urlString`wp://test/source-map.json`;
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
assert.isFalse(sourceMap.hasIgnoreListHint(urlString`wp:///foo.js`));
assert.isTrue(sourceMap.hasIgnoreListHint(urlString`wp:///vendor1.js`));
assert.isTrue(sourceMap.hasIgnoreListHint(urlString`wp:///vendor2.js`));
assert.isTrue(sourceMap.hasIgnoreListHint(urlString`wp:///vendor3.js`));
assert.deepEqual(sourceMap.findRanges(url => sourceMap.hasIgnoreListHint(url)) as [], [
{
startLine: 0,
startColumn: 0,
endLine: 3,
endColumn: 0,
},
]);
});
it('computes ranges for third party code when parts of the script are third-party', () => {
const mappingPayload = encodeSourceMap(
[
// clang-format off
'10:9 => foo.js:1:0',
'11:8 => vendor1.js:1:0',
'12:7 => vendor1.js:1:0',
'13:6 => bar.js:1:0',
'14:5 => vendor1.js:1:0',
'15:4 => vendor2.js:1:0',
'16:3 => vendor1.js:1:0',
'17:2 => foo.js:1:0',
'18:1 => baz.js:1:0',
'19:0 => vendor3.js:1:0', // unknown end
// clang-format on
],
'wp:///' /* sourceRoot */);
mappingPayload.ignoreList = [1 /* vendor1.js */, 3 /* vendor2.js */, 5 /* vendor3.js */];
const sourceMapJsonUrl = urlString`wp://test/source-map.json`;
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
assert.isFalse(sourceMap.hasIgnoreListHint(urlString`wp:///foo.js`));
assert.isFalse(sourceMap.hasIgnoreListHint(urlString`wp:///bar.js`));
assert.isFalse(sourceMap.hasIgnoreListHint(urlString`wp:///baz.js`));
assert.isTrue(sourceMap.hasIgnoreListHint(urlString`wp:///vendor1.js`));
assert.isTrue(sourceMap.hasIgnoreListHint(urlString`wp:///vendor2.js`));
assert.isTrue(sourceMap.hasIgnoreListHint(urlString`wp:///vendor3.js`));
assert.deepEqual(sourceMap.findRanges(url => sourceMap.hasIgnoreListHint(url)) as [], [
{
startLine: 11,
startColumn: 8,
endLine: 13,
endColumn: 6,
},
{
startLine: 14,
startColumn: 5,
endLine: 17,
endColumn: 2,
},
{
startLine: 19,
startColumn: 0,
endLine: 2147483647,
endColumn: 2147483647,
},
]);
});
it('computes ranges when the first mapping is for third-party code that is not on the first char', () => {
const mappingPayload = encodeSourceMap(
[
// clang-format off
'10:9 => vendor1.js:1:0', // initial mapping not at 0:0
'11:8 => vendor2.js:1:0',
'12:7 => vendor3.js:1:0',
'13:6 => foo.js:1:0', // known end
// clang-format on
],
'wp:///' /* sourceRoot */);
mappingPayload.ignoreList = [0 /* vendor1.js */, 1 /* vendor2.js */, 2 /* vendor3.js */];
const sourceMapJsonUrl = urlString`wp://test/source-map.json`;
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
assert.isFalse(sourceMap.hasIgnoreListHint(urlString`wp:///foo.js`));
assert.isTrue(sourceMap.hasIgnoreListHint(urlString`wp:///vendor1.js`));
assert.isTrue(sourceMap.hasIgnoreListHint(urlString`wp:///vendor2.js`));
assert.isTrue(sourceMap.hasIgnoreListHint(urlString`wp:///vendor3.js`));
assert.deepEqual(sourceMap.findRanges(url => sourceMap.hasIgnoreListHint(url)) as [], [
{
startLine: 10, // By default, unmapped code (before 10:9) is not considered
startColumn: 9, // special, and will therefore not be included in the range.
endLine: 13,
endColumn: 6,
},
]);
assert.deepEqual(sourceMap.findRanges(url => sourceMap.hasIgnoreListHint(url), {isStartMatching: true}) as [], [
{
startLine: 0, // Starting at 0:0 instead of 10:9 because all the code until
startColumn: 0, // the initial mapping is now assumed to match the predicate.
endLine: 13,
endColumn: 6,
},
]);
});
it('computes ranges when the first mapping is for first-party code that is not on the first char', () => {
const mappingPayload = encodeSourceMap(
[
// clang-format off
'5:5 => foo.js:1:0', // initial mapping not at 0:0
'10:9 => vendor1.js:1:0',
'11:8 => vendor2.js:1:0',
'12:7 => vendor3.js:1:0',
'13:6 => foo.js:1:0', // known end
// clang-format on
],
'wp:///' /* sourceRoot */);
mappingPayload.ignoreList = [1 /* vendor1.js */, 2 /* vendor2.js */, 3 /* vendor3.js */];
const sourceMapJsonUrl = urlString`wp://test/source-map.json`;
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
assert.isFalse(sourceMap.hasIgnoreListHint(urlString`wp:///foo.js`));
assert.isTrue(sourceMap.hasIgnoreListHint(urlString`wp:///vendor1.js`));
assert.isTrue(sourceMap.hasIgnoreListHint(urlString`wp:///vendor2.js`));
assert.isTrue(sourceMap.hasIgnoreListHint(urlString`wp:///vendor3.js`));
assert.deepEqual(sourceMap.findRanges(url => sourceMap.hasIgnoreListHint(url)) as [], [
{
startLine: 10, // By default, unmapped code (before 5:5) is not considered
startColumn: 9, // special, and will therefore not be included in the range.
endLine: 13,
endColumn: 6,
},
]);
assert.deepEqual(sourceMap.findRanges(url => sourceMap.hasIgnoreListHint(url), {isStartMatching: true}) as [], [
{
startLine: 0, // Starting at 0:0 instead of 10:9 because all the code until
startColumn: 0, // the initial mapping is now assumed to match the predicate.
endLine: 5, // And because the first source url is not hinted as being on
endColumn: 5, // the ignore-list, there's now an extra initial range.
},
{
startLine: 10,
startColumn: 9,
endLine: 13,
endColumn: 6,
},
]);
});
});
describe('loadSourceMap', () => {
const payload: SDK.SourceMap.SourceMapV3 = {version: 3, sources: [], mappings: ''};
const {parseSourceMap} = SDK.SourceMap;
it('can parse sourcemap with BOM at the beginning of the file', () => {
const content = '\uFEFF' + JSON.stringify(payload);
assert.deepEqual(parseSourceMap(content), payload);
});
it('skips over first line when file starts with )]}', () => {
const content = ')]} {"version": 2}\n' + JSON.stringify(payload);
assert.deepEqual(parseSourceMap(content), payload);
});
});
describe('reverseMapTextRanges', () => {
const {SourceMap} = SDK.SourceMap;
const {TextRange} = TextUtils.TextRange;
it('yields an empty array for unknown source URLs', () => {
const sourceMap = new SourceMap(compiledUrl, sourceMapJsonUrl, encodeSourceMap(['0:0 => example.js:0:0']));
assert.isEmpty(sourceMap.reverseMapTextRanges(sourceUrlOther, new TextRange(0, 0, 1, 1)));
});
it('yields a single range for trivial single-line, fully contained mappings', () => {
const sourceMap = new SourceMap(compiledUrl, sourceMapJsonUrl, encodeSourceMap([
'0:0 => example.js:0:0',
'0:5 => example.js:0:6',
'1:0 => other.js:0:0',
'1:8 => other.js:0:9',
]));
const exampleRanges = sourceMap.reverseMapTextRanges(sourceUrlExample, new TextRange(0, 0, 0, 6));
assert.lengthOf(exampleRanges, 1, 'expected a single range');
assert.deepEqual(exampleRanges[0], new TextRange(0, 0, 0, 5));
const otherRanges = sourceMap.reverseMapTextRanges(sourceUrlOther, new TextRange(0, 4, 0, 7));
assert.lengthOf(otherRanges, 1, 'expected a single range');
assert.deepEqual(otherRanges[0], new TextRange(1, 0, 1, 8));
});
it('yields a combined range for adjacent single-line mappings', () => {
const sourceMap = new SourceMap(compiledUrl, sourceMapJsonUrl, encodeSourceMap([
'0:0 => example.js:0:0',
'0:5 => example.js:0:5',
'0:9 => example.js:0:9',
'5:0 => other.js:1:1',
'5:1 => other.js:1:4',
'5:8 => other.js:1:8',
]));
const exampleRanges = sourceMap.reverseMapTextRanges(sourceUrlExample, new TextRange(0, 1, 0, 6));
assert.lengthOf(exampleRanges, 1, 'expected a single range');
assert.deepEqual(exampleRanges[0], new TextRange(0, 0, 0, 9));
const otherRanges = sourceMap.reverseMapTextRanges(sourceUrlOther, new TextRange(1, 1, 1, 7));
assert.lengthOf(otherRanges, 1, 'expected a single range');
assert.deepEqual(otherRanges[0], new TextRange(5, 0, 5, 8));
});
it('yields a combined range for adjacent multi-line mappings', () => {
const sourceMap = new SourceMap(compiledUrl, sourceMapJsonUrl, encodeSourceMap([
'0:0 => example.js:0:0',
'2:5 => example.js:1:5',
'9:9 => example.js:1:9',
]));
let exampleRanges = sourceMap.reverseMapTextRanges(sourceUrlExample, new TextRange(0, 0, 1, 6));
assert.lengthOf(exampleRanges, 1, 'expected a single range');
assert.deepEqual(exampleRanges[0], new TextRange(0, 0, 9, 9));
exampleRanges = sourceMap.reverseMapTextRanges(sourceUrlExample, new TextRange(0, 0, 1, 9));
assert.lengthOf(exampleRanges, 1, 'expected a single range');
assert.deepEqual(exampleRanges[0], new TextRange(0, 0, 9, 9));
});
it('correctly handles exact range matches', () => {
const sourceMap = new SourceMap(compiledUrl, sourceMapJsonUrl, encodeSourceMap([
'0:0 => example.js:0:0',
'0:1 => example.js:0:3',
]));
const exampleRanges = sourceMap.reverseMapTextRanges(sourceUrlExample, new TextRange(0, 0, 0, 3));
assert.lengthOf(exampleRanges, 1, 'expected a single range');
assert.deepEqual(exampleRanges[0], new TextRange(0, 0, 0, 1));
});
it('correctly handles un-mapped prefixes in source files', () => {
const sourceMap = new SourceMap(compiledUrl, sourceMapJsonUrl, encodeSourceMap([
'1:2 => example.js:4:0',
'3:4 => example.js:4:5',
]));
const exampleRanges = sourceMap.reverseMapTextRanges(sourceUrlExample, new TextRange(0, 0, 4, 1));
assert.lengthOf(exampleRanges, 1, 'expected a single range');
assert.deepEqual(exampleRanges[0], new TextRange(1, 2, 3, 4));
});
it('correctly handles un-mapped suffixes in source files', () => {
const sourceMap = new SourceMap(compiledUrl, sourceMapJsonUrl, encodeSourceMap([
'1:2 => example.js:4:0',
'3:4 => example.js:4:5',
]));
let exampleRanges = sourceMap.reverseMapTextRanges(sourceUrlExample, new TextRange(4, 0, 10, 0));
assert.lengthOf(exampleRanges, 1, 'expected a single range');
assert.deepEqual(exampleRanges[0], TextRange.createUnboundedFromLocation(1, 2));
exampleRanges = sourceMap.reverseMapTextRanges(sourceUrlExample, new TextRange(4, 1, 10, 0));
assert.lengthOf(exampleRanges, 1, 'expected a single range');
assert.deepEqual(exampleRanges[0], TextRange.createUnboundedFromLocation(1, 2));
exampleRanges = sourceMap.reverseMapTextRanges(sourceUrlExample, new TextRange(4, 5, 10, 0));
assert.lengthOf(exampleRanges, 1, 'expected a single range');
assert.deepEqual(exampleRanges[0], TextRange.createUnboundedFromLocation(3, 4));
exampleRanges = sourceMap.reverseMapTextRanges(sourceUrlExample, new TextRange(4, 8, 10, 0));
assert.lengthOf(exampleRanges, 1, 'expected a single range');
assert.deepEqual(exampleRanges[0], TextRange.createUnboundedFromLocation(3, 4));
});
it('correctly handles single-line mappings with holes', () => {
const sourceMap = new SourceMap(compiledUrl, sourceMapJsonUrl, encodeSourceMap([
'1:0 => example.js:4:0',
'1:1 => other.js:6:0',
'1:4 => example.js:4:5',
'1:5 => example.js:4:8',
'1:6 => example.js:1:0',
'1:7 => example.js:4:9',
]));
let exampleRanges = sourceMap.reverseMapTextRanges(sourceUrlExample, new TextRange(4, 1, 4, 6));
assert.lengthOf(exampleRanges, 2, 'expected two distinct ranges');
assert.deepEqual(exampleRanges[0], new TextRange(1, 0, 1, 1));
assert.deepEqual(exampleRanges[1], new TextRange(1, 4, 1, 5));
exampleRanges = sourceMap.reverseMapTextRanges(sourceUrlExample, new TextR