[6a3a178] | 1 | /*
|
---|
| 2 | Copyright 2015, Yahoo Inc.
|
---|
| 3 | Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
|
---|
| 4 | */
|
---|
| 5 | 'use strict';
|
---|
| 6 |
|
---|
| 7 | const pathutils = require('./pathutils');
|
---|
| 8 | const {
|
---|
| 9 | GREATEST_LOWER_BOUND,
|
---|
| 10 | LEAST_UPPER_BOUND
|
---|
| 11 | } = require('source-map').SourceMapConsumer;
|
---|
| 12 |
|
---|
| 13 | /**
|
---|
| 14 | * AST ranges are inclusive for start positions and exclusive for end positions.
|
---|
| 15 | * Source maps are also logically ranges over text, though interacting with
|
---|
| 16 | * them is generally achieved by working with explicit positions.
|
---|
| 17 | *
|
---|
| 18 | * When finding the _end_ location of an AST item, the range behavior is
|
---|
| 19 | * important because what we're asking for is the _end_ of whatever range
|
---|
| 20 | * corresponds to the end location we seek.
|
---|
| 21 | *
|
---|
| 22 | * This boils down to the following steps, conceptually, though the source-map
|
---|
| 23 | * library doesn't expose primitives to do this nicely:
|
---|
| 24 | *
|
---|
| 25 | * 1. Find the range on the generated file that ends at, or exclusively
|
---|
| 26 | * contains the end position of the AST node.
|
---|
| 27 | * 2. Find the range on the original file that corresponds to
|
---|
| 28 | * that generated range.
|
---|
| 29 | * 3. Find the _end_ location of that original range.
|
---|
| 30 | */
|
---|
| 31 | function originalEndPositionFor(sourceMap, generatedEnd) {
|
---|
| 32 | // Given the generated location, find the original location of the mapping
|
---|
| 33 | // that corresponds to a range on the generated file that overlaps the
|
---|
| 34 | // generated file end location. Note however that this position on its
|
---|
| 35 | // own is not useful because it is the position of the _start_ of the range
|
---|
| 36 | // on the original file, and we want the _end_ of the range.
|
---|
| 37 | const beforeEndMapping = originalPositionTryBoth(
|
---|
| 38 | sourceMap,
|
---|
| 39 | generatedEnd.line,
|
---|
| 40 | generatedEnd.column - 1
|
---|
| 41 | );
|
---|
| 42 | if (beforeEndMapping.source === null) {
|
---|
| 43 | return null;
|
---|
| 44 | }
|
---|
| 45 |
|
---|
| 46 | // Convert that original position back to a generated one, with a bump
|
---|
| 47 | // to the right, and a rightward bias. Since 'generatedPositionFor' searches
|
---|
| 48 | // for mappings in the original-order sorted list, this will find the
|
---|
| 49 | // mapping that corresponds to the one immediately after the
|
---|
| 50 | // beforeEndMapping mapping.
|
---|
| 51 | const afterEndMapping = sourceMap.generatedPositionFor({
|
---|
| 52 | source: beforeEndMapping.source,
|
---|
| 53 | line: beforeEndMapping.line,
|
---|
| 54 | column: beforeEndMapping.column + 1,
|
---|
| 55 | bias: LEAST_UPPER_BOUND
|
---|
| 56 | });
|
---|
| 57 | if (
|
---|
| 58 | // If this is null, it means that we've hit the end of the file,
|
---|
| 59 | // so we can use Infinity as the end column.
|
---|
| 60 | afterEndMapping.line === null ||
|
---|
| 61 | // If these don't match, it means that the call to
|
---|
| 62 | // 'generatedPositionFor' didn't find any other original mappings on
|
---|
| 63 | // the line we gave, so consider the binding to extend to infinity.
|
---|
| 64 | sourceMap.originalPositionFor(afterEndMapping).line !==
|
---|
| 65 | beforeEndMapping.line
|
---|
| 66 | ) {
|
---|
| 67 | return {
|
---|
| 68 | source: beforeEndMapping.source,
|
---|
| 69 | line: beforeEndMapping.line,
|
---|
| 70 | column: Infinity
|
---|
| 71 | };
|
---|
| 72 | }
|
---|
| 73 |
|
---|
| 74 | // Convert the end mapping into the real original position.
|
---|
| 75 | return sourceMap.originalPositionFor(afterEndMapping);
|
---|
| 76 | }
|
---|
| 77 |
|
---|
| 78 | /**
|
---|
| 79 | * Attempts to determine the original source position, first
|
---|
| 80 | * returning the closest element to the left (GREATEST_LOWER_BOUND),
|
---|
| 81 | * and next returning the closest element to the right (LEAST_UPPER_BOUND).
|
---|
| 82 | */
|
---|
| 83 | function originalPositionTryBoth(sourceMap, line, column) {
|
---|
| 84 | const mapping = sourceMap.originalPositionFor({
|
---|
| 85 | line,
|
---|
| 86 | column,
|
---|
| 87 | bias: GREATEST_LOWER_BOUND
|
---|
| 88 | });
|
---|
| 89 | if (mapping.source === null) {
|
---|
| 90 | return sourceMap.originalPositionFor({
|
---|
| 91 | line,
|
---|
| 92 | column,
|
---|
| 93 | bias: LEAST_UPPER_BOUND
|
---|
| 94 | });
|
---|
| 95 | } else {
|
---|
| 96 | return mapping;
|
---|
| 97 | }
|
---|
| 98 | }
|
---|
| 99 |
|
---|
| 100 | function isInvalidPosition(pos) {
|
---|
| 101 | return (
|
---|
| 102 | !pos ||
|
---|
| 103 | typeof pos.line !== 'number' ||
|
---|
| 104 | typeof pos.column !== 'number' ||
|
---|
| 105 | pos.line < 0 ||
|
---|
| 106 | pos.column < 0
|
---|
| 107 | );
|
---|
| 108 | }
|
---|
| 109 |
|
---|
| 110 | /**
|
---|
| 111 | * determines the original position for a given location
|
---|
| 112 | * @param {SourceMapConsumer} sourceMap the source map
|
---|
| 113 | * @param {Object} generatedLocation the original location Object
|
---|
| 114 | * @returns {Object} the remapped location Object
|
---|
| 115 | */
|
---|
| 116 | function getMapping(sourceMap, generatedLocation, origFile) {
|
---|
| 117 | if (!generatedLocation) {
|
---|
| 118 | return null;
|
---|
| 119 | }
|
---|
| 120 |
|
---|
| 121 | if (
|
---|
| 122 | isInvalidPosition(generatedLocation.start) ||
|
---|
| 123 | isInvalidPosition(generatedLocation.end)
|
---|
| 124 | ) {
|
---|
| 125 | return null;
|
---|
| 126 | }
|
---|
| 127 |
|
---|
| 128 | const start = originalPositionTryBoth(
|
---|
| 129 | sourceMap,
|
---|
| 130 | generatedLocation.start.line,
|
---|
| 131 | generatedLocation.start.column
|
---|
| 132 | );
|
---|
| 133 | let end = originalEndPositionFor(sourceMap, generatedLocation.end);
|
---|
| 134 |
|
---|
| 135 | /* istanbul ignore if: edge case too hard to test for */
|
---|
| 136 | if (!(start && end)) {
|
---|
| 137 | return null;
|
---|
| 138 | }
|
---|
| 139 |
|
---|
| 140 | if (!(start.source && end.source)) {
|
---|
| 141 | return null;
|
---|
| 142 | }
|
---|
| 143 |
|
---|
| 144 | if (start.source !== end.source) {
|
---|
| 145 | return null;
|
---|
| 146 | }
|
---|
| 147 |
|
---|
| 148 | /* istanbul ignore if: edge case too hard to test for */
|
---|
| 149 | if (start.line === null || start.column === null) {
|
---|
| 150 | return null;
|
---|
| 151 | }
|
---|
| 152 |
|
---|
| 153 | /* istanbul ignore if: edge case too hard to test for */
|
---|
| 154 | if (end.line === null || end.column === null) {
|
---|
| 155 | return null;
|
---|
| 156 | }
|
---|
| 157 |
|
---|
| 158 | if (start.line === end.line && start.column === end.column) {
|
---|
| 159 | end = sourceMap.originalPositionFor({
|
---|
| 160 | line: generatedLocation.end.line,
|
---|
| 161 | column: generatedLocation.end.column,
|
---|
| 162 | bias: LEAST_UPPER_BOUND
|
---|
| 163 | });
|
---|
| 164 | end.column -= 1;
|
---|
| 165 | }
|
---|
| 166 |
|
---|
| 167 | return {
|
---|
| 168 | source: pathutils.relativeTo(start.source, origFile),
|
---|
| 169 | loc: {
|
---|
| 170 | start: {
|
---|
| 171 | line: start.line,
|
---|
| 172 | column: start.column
|
---|
| 173 | },
|
---|
| 174 | end: {
|
---|
| 175 | line: end.line,
|
---|
| 176 | column: end.column
|
---|
| 177 | }
|
---|
| 178 | }
|
---|
| 179 | };
|
---|
| 180 | }
|
---|
| 181 |
|
---|
| 182 | module.exports = getMapping;
|
---|