183 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			183 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*
 | |
|  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;
 |