UNPKG

2.44 kBJavaScriptView Raw
1function parse(file) {
2 const fs = require('fs');
3 const acorn = require('acorn');
4 const walk = require('acorn-walk');
5
6 const ast = acorn.parse(fs.readFileSync(file, 'utf8'), {
7 ecmaVersion: 11,
8 locations: true
9 });
10
11 const locations = [];
12 walk.simple(ast, {
13 CallExpression(node) {
14 locations.push(node.loc);
15 }
16 });
17
18 // Walking is depth-first, but we want to sort these breadth-first.
19 locations.sort((a, b) => {
20 if (a.start.line === b.start.line) {
21 return a.start.column - b.start.column;
22 }
23
24 return a.start.line - b.start.line;
25 });
26
27 return locations;
28}
29
30function findTest(locations, declaration) {
31 // Find all calls that span the test declaration.
32 const spans = locations.filter(loc => {
33 if (loc.start.line > declaration.line || loc.end.line < declaration.line) {
34 return false;
35 }
36
37 if (loc.start.line === declaration.line && loc.start.column > declaration.column) {
38 return false;
39 }
40
41 if (loc.end.line === declaration.line && loc.end.column < declaration.column) {
42 return false;
43 }
44
45 return true;
46 });
47
48 // Locations should be sorted by source order, so the last span must be the test.
49 return spans.pop();
50}
51
52const range = (start, end) => new Array(end - start + 1).fill(start).map((element, index) => element + index);
53
54module.exports = ({file, lineNumbers = []}) => {
55 if (lineNumbers.length === 0) {
56 return undefined;
57 }
58
59 // Avoid loading these until we actually need to select tests by line number.
60 const callsites = require('callsites');
61 const sourceMapSupport = require('source-map-support');
62
63 const locations = parse(file);
64 const selected = new Set(lineNumbers);
65
66 return () => {
67 // Assume this is called from a test declaration, which is located in the file.
68 // If not… don't select the test!
69 const callSite = callsites().find(callSite => callSite.getFileName() === file);
70 if (!callSite) {
71 return false;
72 }
73
74 // FIXME: This assumes the callSite hasn't already been adjusted. It's likely
75 // that if `source-map-support/register` has been loaded, this would result
76 // in the wrong location.
77 const sourceCallSite = sourceMapSupport.wrapCallSite(callSite);
78 const start = {
79 line: sourceCallSite.getLineNumber(),
80 column: sourceCallSite.getColumnNumber() - 1 // Use 0-indexed columns.
81 };
82
83 const test = findTest(locations, start);
84 if (!test) {
85 return false;
86 }
87
88 return range(test.start.line, test.end.line).some(line => selected.has(line));
89 };
90};