[go: nahoru, domu]

blob: 9c7455b84bf9d33332e3fad2d01e76b5a997693e [file] [log] [blame]
// 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.
const {assert} = chai;
import type * as Platform from '../../../../../front_end/core/platform/platform.js';
import {assertNotNullOrUndefined} from '../../../../../front_end/core/platform/platform.js';
import * as SDK from '../../../../../front_end/core/sdk/sdk.js';
import * as TextUtils from '../../../../../front_end/models/text_utils/text_utils.js';
import {encodeSourceMap} from '../../helpers/SourceMapEncoder.js';
const sourceUrlFoo = '<foo>' as Platform.DevToolsPath.UrlString;
describe('SourceMapEntry', () => {
it('can be instantiated correctly', () => {
const sourceMapEntry = new SDK.SourceMap.SourceMapEntry(
1, 1, 'http://www.example.com/' as Platform.DevToolsPath.UrlString, 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, 'http://www.example.com/' as Platform.DevToolsPath.UrlString,
'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, sourceUrlFoo, 1, 5, 'foo');
const sourceMapEntry2 = new SDK.SourceMap.SourceMapEntry(2, 5, 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, sourceUrlFoo, 1, 5, 'foo');
const sourceMapEntry2 = new SDK.SourceMap.SourceMapEntry(2, 25, 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, sourceUrlFoo, 1, 5, 'foo');
const sourceMapEntry2 = new SDK.SourceMap.SourceMapEntry(2, 5, sourceUrlFoo, 1, 5, 'foo');
assert.strictEqual(SDK.SourceMap.SourceMapEntry.compare(sourceMapEntry1, sourceMapEntry2), 0);
});
});
});
describe('SourceMap', () => {
const compiledUrl = 'compiled.js' as Platform.DevToolsPath.UrlString;
const sourceMapJsonUrl = 'source-map.json' as Platform.DevToolsPath.UrlString;
const sourceUrlExample = 'example.js' as Platform.DevToolsPath.UrlString;
const sourceUrlOther = 'other.js' as Platform.DevToolsPath.UrlString;
describe('StringCharIterator', () => {
it('detects when it has reached the end', () => {
const emptyIterator = new SDK.SourceMap.SourceMap.StringCharIterator('');
assert.isFalse(emptyIterator.hasNext());
const iterator = new SDK.SourceMap.SourceMap.StringCharIterator('foo');
assert.isTrue(iterator.hasNext());
});
it('peeks the next character', () => {
const emptyIterator = new SDK.SourceMap.SourceMap.StringCharIterator('');
assert.strictEqual(emptyIterator.peek(), '');
const iterator = new SDK.SourceMap.SourceMap.StringCharIterator('foo');
assert.strictEqual(iterator.peek(), 'f');
});
it('advances when {next} is called', () => {
const iterator = new SDK.SourceMap.SourceMap.StringCharIterator('bar');
assert.strictEqual(iterator.next(), 'b');
assert.strictEqual(iterator.next(), 'a');
assert.strictEqual(iterator.next(), 'r');
assert.isFalse(iterator.hasNext());
});
});
function assertMapping(
actual: SDK.SourceMap.SourceMapEntry|null, expectedSourceURL: string|undefined,
expectedSourceLineNumber: number|undefined, expectedSourceColumnNumber: number|undefined) {
assertNotNullOrUndefined(actual);
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) {
assertNotNullOrUndefined(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), 'example.js', 0, 9);
assertMapping(sourceMap.findEntry(0, 13), 'example.js', 0, 13);
assertMapping(sourceMap.findEntry(0, 15), 'example.js', 0, 25);
assertMapping(sourceMap.findEntry(0, 18), 'example.js', 2, 4);
assertMapping(sourceMap.findEntry(0, 25), 'example.js', 2, 11);
assertMapping(sourceMap.findEntry(0, 27), 'example.js', 2, 24);
assertMapping(sourceMap.findEntry(1, 0), 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), 'example.js', 0, 0);
assertMapping(sourceMap.findEntry(0, 2), 'example.js', 0, 2);
const emptyEntry = sourceMap.findEntry(0, 1);
assertNotNullOrUndefined(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), '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), 'example.js', 0, 3);
assertMapping(sourceMap.findEntry(0, 1), 'example.js', 0, 2);
assertMapping(sourceMap.findEntry(0, 2), 'example.js', 0, 1);
assertMapping(sourceMap.findEntry(0, 3), '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), 'source1.js', 0, 0);
assertMapping(sourceMap.findEntry(0, 1), 'source1.js', 2, 1);
assertMapping(sourceMap.findEntry(2, 10), 'source3.js', 0, 0);
assertMapping(sourceMap.findEntry(2, 11), '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 = 'wp://test/source-map.json' as Platform.DevToolsPath.UrlString;
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
assertMapping(sourceMap.findEntry(1, 0), 'wp:///example.js', 3, 0);
assertMapping(sourceMap.findEntry(4, 0), 'wp:///other.js', 5, 0);
});
describe('compatibleForURL', () => {
const compiledURL = 'http://example.com/foo.js' as Platform.DevToolsPath.UrlString;
const sourceMappingURL = `${compiledURL}.map` as Platform.DevToolsPath.UrlString;
const sourceRoot = 'webpack:///src';
const sourceURL = `${sourceRoot}/foo.ts` as Platform.DevToolsPath.UrlString;
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,
'x_google_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, sourceMapURL as Platform.DevToolsPath.UrlString, 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 = 'http://localhost:12345/index.js' as Platform.DevToolsPath.UrlString;
const sourceMappingURL = `${sourceURL}.map` as Platform.DevToolsPath.UrlString;
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 `x_google_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.x_google_ignoreList = [0 /* vendor.js */, 3 /* other.js */];
const sourceMapJsonUrl = 'wp://test/source-map.json' as Platform.DevToolsPath.UrlString;
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///vendor.js' as Platform.DevToolsPath.UrlString), true);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///main.js' as Platform.DevToolsPath.UrlString), false);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///example.js' as Platform.DevToolsPath.UrlString), false);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///other.js' as Platform.DevToolsPath.UrlString), true);
});
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.x_google_ignoreList = [0 /* vendor1.js */, 1 /* vendor2.js */, 2 /* vendor3.js */];
const sourceMapJsonUrl = 'wp://test/source-map.json' as Platform.DevToolsPath.UrlString;
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///foo.js' as Platform.DevToolsPath.UrlString), false);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///vendor1.js' as Platform.DevToolsPath.UrlString), true);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///vendor2.js' as Platform.DevToolsPath.UrlString), true);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///vendor3.js' as Platform.DevToolsPath.UrlString), true);
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.x_google_ignoreList = [1 /* vendor1.js */, 3 /* vendor2.js */, 5 /* vendor3.js */];
const sourceMapJsonUrl = 'wp://test/source-map.json' as Platform.DevToolsPath.UrlString;
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///foo.js' as Platform.DevToolsPath.UrlString), false);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///bar.js' as Platform.DevToolsPath.UrlString), false);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///baz.js' as Platform.DevToolsPath.UrlString), false);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///vendor1.js' as Platform.DevToolsPath.UrlString), true);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///vendor2.js' as Platform.DevToolsPath.UrlString), true);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///vendor3.js' as Platform.DevToolsPath.UrlString), true);
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.x_google_ignoreList = [0 /* vendor1.js */, 1 /* vendor2.js */, 2 /* vendor3.js */];
const sourceMapJsonUrl = 'wp://test/source-map.json' as Platform.DevToolsPath.UrlString;
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///foo.js' as Platform.DevToolsPath.UrlString), false);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///vendor1.js' as Platform.DevToolsPath.UrlString), true);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///vendor2.js' as Platform.DevToolsPath.UrlString), true);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///vendor3.js' as Platform.DevToolsPath.UrlString), true);
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.x_google_ignoreList = [1 /* vendor1.js */, 2 /* vendor2.js */, 3 /* vendor3.js */];
const sourceMapJsonUrl = 'wp://test/source-map.json' as Platform.DevToolsPath.UrlString;
const sourceMap = new SDK.SourceMap.SourceMap(compiledUrl, sourceMapJsonUrl, mappingPayload);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///foo.js' as Platform.DevToolsPath.UrlString), false);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///vendor1.js' as Platform.DevToolsPath.UrlString), true);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///vendor2.js' as Platform.DevToolsPath.UrlString), true);
assert.strictEqual(sourceMap.hasIgnoreListHint('wp:///vendor3.js' as Platform.DevToolsPath.UrlString), true);
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 TextRange(4, 1, 4, 9));
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, 6));
});
it('correctly handles overlapping mappings', () => {
// This presents a really weird example, which we believe is unlikely to be relevant
// in practice. But just in case, this test case serves as a documentation for how
// DevTools will currently resolve this case: It will purely go by the reverse
// mapping to figure out which chunks belong together and then afterwards will
// try to combine mappings in the order in which the reverse mappings are sorted.
const sourceMap = new SourceMap(compiledUrl, sourceMapJsonUrl, encodeSourceMap([
'1:0 => example.js:1:0',
'1:4 => example.js:1:5',
'1:5 => example.js:1:8',
'2:6 => example.js:1:1',
'2:7 => example.js:1:9',
]));
const exampleRanges = sourceMap.reverseMapTextRanges(sourceUrlExample, new TextRange(1, 2, 1, 7));
assert.lengthOf(exampleRanges, 2, 'expected two distinct ranges');
assert.deepEqual(exampleRanges[0], new TextRange(1, 4, 1, 5));
assert.deepEqual(exampleRanges[1], new TextRange(2, 6, 2, 7));
});
it('correctly sorts and merges adjacent mappings even if source map is unsorted', () => {
// This is a slight variation of the previous test. We want to ensure that
// no matter how crazy the source map, we always yield a maximally merged
// and sorted result.
const sourceMap = new SourceMap(compiledUrl, sourceMapJsonUrl, encodeSourceMap([
'1:0 => example.js:1:0',
'1:5 => example.js:1:8',
'2:6 => example.js:1:1',
'2:7 => example.js:1:9',
]));
const exampleRanges = sourceMap.reverseMapTextRanges(sourceUrlExample, new TextRange(1, 0, 1, 9));
assert.lengthOf(exampleRanges, 1, 'expected a single maximally merged range');
assert.deepEqual(exampleRanges[0], new TextRange(1, 0, 2, 7));
});
});
});