| /* |
| Copyright 2015, Yahoo Inc. |
| Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. |
| */ |
| 'use strict'; |
| |
| const pathutils = require('./pathutils'); |
| const { |
| GREATEST_LOWER_BOUND, |
| LEAST_UPPER_BOUND |
| } = require('source-map').SourceMapConsumer; |
| |
| /** |
| * AST ranges are inclusive for start positions and exclusive for end positions. |
| * Source maps are also logically ranges over text, though interacting with |
| * them is generally achieved by working with explicit positions. |
| * |
| * When finding the _end_ location of an AST item, the range behavior is |
| * important because what we're asking for is the _end_ of whatever range |
| * corresponds to the end location we seek. |
| * |
| * This boils down to the following steps, conceptually, though the source-map |
| * library doesn't expose primitives to do this nicely: |
| * |
| * 1. Find the range on the generated file that ends at, or exclusively |
| * contains the end position of the AST node. |
| * 2. Find the range on the original file that corresponds to |
| * that generated range. |
| * 3. Find the _end_ location of that original range. |
| */ |
| function originalEndPositionFor(sourceMap, generatedEnd) { |
| // Given the generated location, find the original location of the mapping |
| // that corresponds to a range on the generated file that overlaps the |
| // generated file end location. Note however that this position on its |
| // own is not useful because it is the position of the _start_ of the range |
| // on the original file, and we want the _end_ of the range. |
| const beforeEndMapping = originalPositionTryBoth( |
| sourceMap, |
| generatedEnd.line, |
| generatedEnd.column - 1 |
| ); |
| if (beforeEndMapping.source === null) { |
| return null; |
| } |
| |
| // Convert that original position back to a generated one, with a bump |
| // to the right, and a rightward bias. Since 'generatedPositionFor' searches |
| // for mappings in the original-order sorted list, this will find the |
| // mapping that corresponds to the one immediately after the |
| // beforeEndMapping mapping. |
| const afterEndMapping = sourceMap.generatedPositionFor({ |
| source: beforeEndMapping.source, |
| line: beforeEndMapping.line, |
| column: beforeEndMapping.column + 1, |
| bias: LEAST_UPPER_BOUND |
| }); |
| if ( |
| // If this is null, it means that we've hit the end of the file, |
| // so we can use Infinity as the end column. |
| afterEndMapping.line === null || |
| // If these don't match, it means that the call to |
| // 'generatedPositionFor' didn't find any other original mappings on |
| // the line we gave, so consider the binding to extend to infinity. |
| sourceMap.originalPositionFor(afterEndMapping).line !== |
| beforeEndMapping.line |
| ) { |
| return { |
| source: beforeEndMapping.source, |
| line: beforeEndMapping.line, |
| column: Infinity |
| }; |
| } |
| |
| // Convert the end mapping into the real original position. |
| return sourceMap.originalPositionFor(afterEndMapping); |
| } |
| |
| /** |
| * Attempts to determine the original source position, first |
| * returning the closest element to the left (GREATEST_LOWER_BOUND), |
| * and next returning the closest element to the right (LEAST_UPPER_BOUND). |
| */ |
| function originalPositionTryBoth(sourceMap, line, column) { |
| const mapping = sourceMap.originalPositionFor({ |
| line, |
| column, |
| bias: GREATEST_LOWER_BOUND |
| }); |
| if (mapping.source === null) { |
| return sourceMap.originalPositionFor({ |
| line, |
| column, |
| bias: LEAST_UPPER_BOUND |
| }); |
| } else { |
| return mapping; |
| } |
| } |
| |
| function isInvalidPosition(pos) { |
| return ( |
| !pos || |
| typeof pos.line !== 'number' || |
| typeof pos.column !== 'number' || |
| pos.line < 0 || |
| pos.column < 0 |
| ); |
| } |
| |
| /** |
| * determines the original position for a given location |
| * @param {SourceMapConsumer} sourceMap the source map |
| * @param {Object} generatedLocation the original location Object |
| * @returns {Object} the remapped location Object |
| */ |
| function getMapping(sourceMap, generatedLocation, origFile) { |
| if (!generatedLocation) { |
| return null; |
| } |
| |
| if ( |
| isInvalidPosition(generatedLocation.start) || |
| isInvalidPosition(generatedLocation.end) |
| ) { |
| return null; |
| } |
| |
| const start = originalPositionTryBoth( |
| sourceMap, |
| generatedLocation.start.line, |
| generatedLocation.start.column |
| ); |
| let end = originalEndPositionFor(sourceMap, generatedLocation.end); |
| |
| /* istanbul ignore if: edge case too hard to test for */ |
| if (!(start && end)) { |
| return null; |
| } |
| |
| if (!(start.source && end.source)) { |
| return null; |
| } |
| |
| if (start.source !== end.source) { |
| return null; |
| } |
| |
| /* istanbul ignore if: edge case too hard to test for */ |
| if (start.line === null || start.column === null) { |
| return null; |
| } |
| |
| /* istanbul ignore if: edge case too hard to test for */ |
| if (end.line === null || end.column === null) { |
| return null; |
| } |
| |
| if (start.line === end.line && start.column === end.column) { |
| end = sourceMap.originalPositionFor({ |
| line: generatedLocation.end.line, |
| column: generatedLocation.end.column, |
| bias: LEAST_UPPER_BOUND |
| }); |
| end.column -= 1; |
| } |
| |
| return { |
| source: pathutils.relativeTo(start.source, origFile), |
| loc: { |
| start: { |
| line: start.line, |
| column: start.column |
| }, |
| end: { |
| line: end.line, |
| column: end.column |
| } |
| } |
| }; |
| } |
| |
| module.exports = getMapping; |