diff --git a/lib/quick-sort.js b/lib/quick-sort.js index 6a7caadb..23f9eda5 100644 --- a/lib/quick-sort.js +++ b/lib/quick-sort.js @@ -15,6 +15,8 @@ // sorting in C++. By using our own JS-implemented Quick Sort (below), we get // a ~3500ms mean speed-up in `bench/bench.html`. +function SortTemplate(comparator) { + /** * Swap the elements indexed by `x` and `y` in the array `ary`. * @@ -85,7 +87,7 @@ function doQuickSort(ary, comparator, p, r) { // // * Every element in `ary[i+1 .. j-1]` is greater than the pivot. for (var j = p; j < r; j++) { - if (comparator(ary[j], pivot) <= 0) { + if (comparator(ary[j], pivot, false) <= 0) { i += 1; swap(ary, i, j); } @@ -101,6 +103,15 @@ function doQuickSort(ary, comparator, p, r) { } } + return doQuickSort; +} + +function cloneSort(comparator) { + let template = SortTemplate.toString(); + let templateFn = new Function(`return ${template}`)(); + return templateFn(comparator); +} + /** * Sort the given array in-place with the given comparator function. * @@ -109,6 +120,13 @@ function doQuickSort(ary, comparator, p, r) { * @param {function} comparator * Function to use to compare two items. */ -exports.quickSort = function (ary, comparator) { - doQuickSort(ary, comparator, 0, ary.length - 1); + +let sortCache = new WeakMap(); +exports.quickSort = function (ary, comparator, start = 0) { + let doQuickSort = sortCache.get(comparator); + if (doQuickSort === void 0) { + doQuickSort = cloneSort(comparator); + sortCache.set(comparator, doQuickSort); + } + doQuickSort(ary, comparator, start, ary.length - 1); }; diff --git a/lib/source-map-consumer.js b/lib/source-map-consumer.js index 7b99d1da..17bd8c3f 100644 --- a/lib/source-map-consumer.js +++ b/lib/source-map-consumer.js @@ -464,6 +464,36 @@ function Mapping() { * query (the ordered arrays in the `this.__generatedMappings` and * `this.__originalMappings` properties). */ + +const compareGenerated = util.compareByGeneratedPositionsDeflatedNoLine; +function sortGenerated(array, start) { + let l = array.length; + let n = array.length - start; + if (n <= 1) { + return; + } else if (n == 2) { + let a = array[start]; + let b = array[start + 1]; + if (compareGenerated(a, b) > 0) { + array[start] = b; + array[start + 1] = a; + } + } else if (n < 20) { + for (let i = start; i < l; i++) { + for (let j = i; j > start; j--) { + let a = array[j - 1]; + let b = array[j]; + if (compareGenerated(a, b) <= 0) { + break; + } + array[j - 1] = b; + array[j] = a; + } + } + } else { + quickSort(array, compareGenerated, start); + } +} BasicSourceMapConsumer.prototype._parseMappings = function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { var generatedLine = 1; @@ -480,11 +510,15 @@ BasicSourceMapConsumer.prototype._parseMappings = var generatedMappings = []; var mapping, str, segment, end, value; + let subarrayStart = 0; while (index < length) { if (aStr.charAt(index) === ';') { generatedLine++; index++; previousGeneratedColumn = 0; + + sortGenerated(generatedMappings, subarrayStart); + subarrayStart = generatedMappings.length; } else if (aStr.charAt(index) === ',') { index++; @@ -493,11 +527,6 @@ BasicSourceMapConsumer.prototype._parseMappings = mapping = new Mapping(); mapping.generatedLine = generatedLine; - // Because each offset is encoded relative to the previous one, - // many segments often have the same encoding. We can exploit this - // fact by caching the parsed variable length fields of each segment, - // allowing us to avoid a second parse if we encounter the same - // segment again. for (end = index; end < length; end++) { if (this._charIsMappingSeparator(aStr, end)) { break; @@ -505,27 +534,20 @@ BasicSourceMapConsumer.prototype._parseMappings = } str = aStr.slice(index, end); - segment = cachedSegments[str]; - if (segment) { - index += str.length; - } else { - segment = []; - while (index < end) { - base64VLQ.decode(aStr, index, temp); - value = temp.value; - index = temp.rest; - segment.push(value); - } - - if (segment.length === 2) { - throw new Error('Found a source, but no line and column'); - } + segment = []; + while (index < end) { + base64VLQ.decode(aStr, index, temp); + value = temp.value; + index = temp.rest; + segment.push(value); + } - if (segment.length === 3) { - throw new Error('Found a source and line, but no column'); - } + if (segment.length === 2) { + throw new Error('Found a source, but no line and column'); + } - cachedSegments[str] = segment; + if (segment.length === 3) { + throw new Error('Found a source and line, but no column'); } // Generated column. @@ -556,16 +578,27 @@ BasicSourceMapConsumer.prototype._parseMappings = generatedMappings.push(mapping); if (typeof mapping.originalLine === 'number') { - originalMappings.push(mapping); + let currentSource = mapping.source; + while (originalMappings.length <= currentSource) { + originalMappings.push(null); + } + if (originalMappings[currentSource] === null) { + originalMappings[currentSource] = []; + } + originalMappings[currentSource].push(mapping); } } } - quickSort(generatedMappings, util.compareByGeneratedPositionsDeflated); + sortGenerated(generatedMappings, subarrayStart); this.__generatedMappings = generatedMappings; - quickSort(originalMappings, util.compareByOriginalPositions); - this.__originalMappings = originalMappings; + for (var i = 0; i < originalMappings.length; i++) { + if (originalMappings[i] != null) { + quickSort(originalMappings[i], util.compareByOriginalPositionsNoSource); + } + } + this.__originalMappings = [].concat(...originalMappings); }; /** diff --git a/lib/util.js b/lib/util.js index 94e85765..430e2d0f 100644 --- a/lib/util.js +++ b/lib/util.js @@ -385,6 +385,33 @@ function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) { } exports.compareByOriginalPositions = compareByOriginalPositions; +function compareByOriginalPositionsNoSource(mappingA, mappingB, onlyCompareOriginal) { + var cmp + + cmp = mappingA.originalLine - mappingB.originalLine; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalColumn - mappingB.originalColumn; + if (cmp !== 0 || onlyCompareOriginal) { + return cmp; + } + + cmp = mappingA.generatedColumn - mappingB.generatedColumn; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.generatedLine - mappingB.generatedLine; + if (cmp !== 0) { + return cmp; + } + + return strcmp(mappingA.name, mappingB.name); +} +exports.compareByOriginalPositionsNoSource = compareByOriginalPositionsNoSource; + /** * Comparator between two mappings with deflated source and name indices where * the generated positions are compared. @@ -424,6 +451,31 @@ function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGene } exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated; +function compareByGeneratedPositionsDeflatedNoLine(mappingA, mappingB, onlyCompareGenerated) { + var cmp = mappingA.generatedColumn - mappingB.generatedColumn; + if (cmp !== 0 || onlyCompareGenerated) { + return cmp; + } + + cmp = strcmp(mappingA.source, mappingB.source); + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalLine - mappingB.originalLine; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalColumn - mappingB.originalColumn; + if (cmp !== 0) { + return cmp; + } + + return strcmp(mappingA.name, mappingB.name); +} +exports.compareByGeneratedPositionsDeflatedNoLine = compareByGeneratedPositionsDeflatedNoLine; + function strcmp(aStr1, aStr2) { if (aStr1 === aStr2) { return 0;