scope-utils.js (3374B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 /** 8 * Check if the position is within this function location 9 * 10 * @param {object} functionLocation 11 * @param {object} position 12 * @returns {boolean} 13 */ 14 function containsPosition(functionLocation, position) { 15 // Start 16 return ( 17 (functionLocation.start.line < position.line || 18 // If the start line is equal check the columns 19 (functionLocation.start.line == position.line && 20 functionLocation.start.column <= position.column)) && 21 // End 22 (functionLocation.end.line > position.line || 23 // If the end line is equal check the columns 24 (functionLocation.end.line == position.line && 25 functionLocation.end.column >= position.column)) 26 ); 27 } 28 29 function containsLocation(parentLocation, childLocation) { 30 return ( 31 containsPosition(parentLocation, childLocation.start) && 32 containsPosition(parentLocation, childLocation.end) 33 ); 34 } 35 36 function getInnerLocations(locations, position) { 37 // First, find the function which directly contains the specified position (line / column) 38 let parentIndex; 39 for (let i = locations.length - 1; i >= 0; i--) { 40 if (containsPosition(locations[i], position)) { 41 parentIndex = i; 42 break; 43 } 44 } 45 46 if (parentIndex == undefined) { 47 return []; 48 } 49 50 const parentLoc = locations[parentIndex]; 51 52 // Then, from the nearest location, loop locations again and put locations into 53 // the innerLocations array until we get to a location not enclosed by the nearest location. 54 const innerLocations = []; 55 for (let i = parentIndex + 1; i < locations.length; i++) { 56 const loc = locations[i]; 57 if (!containsLocation(parentLoc, loc)) { 58 break; 59 } 60 innerLocations.push(loc); 61 } 62 63 return innerLocations; 64 } 65 66 /** 67 * Sort based on the start line 68 * 69 * @param {Array} locations 70 * @returns 71 */ 72 function sortByStart(locations) { 73 return locations.sort((a, b) => { 74 if (a.startLine < b.startLine) { 75 return -1; 76 } else if (a.startLine === b.startLine) { 77 return b.endLine - a.endLine; 78 } 79 return 1; 80 }); 81 } 82 83 /** 84 * Return a new locations array which excludes 85 * items that are completely enclosed by another location in the input locations 86 * 87 * @param locations Notice! The locations MUST be sorted by `sortByStart` 88 * so that we can do linear time complexity operation. 89 */ 90 function removeOverlapLocations(locations) { 91 if (!locations.length) { 92 return []; 93 } 94 const firstParent = locations[0]; 95 return locations.reduce(deduplicateNode, [firstParent]); 96 } 97 98 function deduplicateNode(nodes, location) { 99 const parent = nodes[nodes.length - 1]; 100 if (!containsLocation(parent, location)) { 101 nodes.push(location); 102 } 103 return nodes; 104 } 105 106 function getOutOfScopeLines(outOfScopeLocations) { 107 if (!outOfScopeLocations) { 108 return new Set(); 109 } 110 111 const uniqueLines = new Set(); 112 for (const location of outOfScopeLocations) { 113 for (let i = location.startLine; i < location.endLine; i++) { 114 uniqueLines.add(i); 115 } 116 } 117 118 return uniqueLines; 119 } 120 121 module.exports = { 122 containsPosition, 123 containsLocation, 124 getInnerLocations, 125 removeOverlapLocations, 126 getOutOfScopeLines, 127 sortByStart, 128 };