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; |