UNPKG

3.52 kBJavaScriptView Raw
1// @ts-check
2
3/**
4 * Gets info about a source code range, including line/column numbers and if
5 * it’s ignored by a comment.
6 * @param {string} source Source code.
7 * @param {number} startOffset Start character offset.
8 * @param {number} endOffset End character offset.
9 * @param {string | false} [ignoreNextLineComment] Single line
10 * case-insensitive comment content to ignore ranges that start on the the
11 * next line, or `false` to disable ignore comments. Defaults to
12 * `" coverage ignore next line"`.
13 * @returns {SourceCodeRange} Source code range info.
14 */
15export default function sourceRange(
16 source,
17 startOffset,
18 endOffset,
19 ignoreNextLineComment = " coverage ignore next line"
20) {
21 if (typeof source !== "string")
22 throw new TypeError("Argument 1 `source` must be a string.");
23
24 if (typeof startOffset !== "number")
25 throw new TypeError("Argument 2 `startOffset` must be a number.");
26
27 if (typeof endOffset !== "number")
28 throw new TypeError("Argument 3 `endOffset` must be a number.");
29
30 if (
31 typeof ignoreNextLineComment !== "string" &&
32 ignoreNextLineComment !== false
33 )
34 throw new TypeError(
35 "Argument 4 `ignoreNextLineComment` must be a string or `false`."
36 );
37
38 const ignoreNextLineCommentLowerCase = ignoreNextLineComment
39 ? `//${ignoreNextLineComment.toLowerCase()}`
40 : null;
41
42 /** @type {SourceCodeRange["ignore"]} */
43 let ignore = false;
44
45 /** @type {SourceCodeLocation["line"] | undefined} */
46 let startLine;
47
48 /** @type {SourceCodeLocation["column"] | undefined} */
49 let startColumn;
50
51 /** @type {SourceCodeLocation["line"] | undefined} */
52 let endLine;
53
54 /** @type {SourceCodeLocation["column"] | undefined} */
55 let endColumn;
56
57 const lines = source.split(/^/gmu);
58
59 let lineOffset = 0;
60
61 for (const [lineIndex, lineSource] of lines.entries()) {
62 const nextLineOffset = lineOffset + lineSource.length;
63
64 if (
65 !startLine &&
66 startOffset >= lineOffset &&
67 startOffset < nextLineOffset
68 ) {
69 startLine = lineIndex + 1;
70 startColumn = startOffset - lineOffset + 1;
71
72 if (
73 // Ignoring is enabled.
74 ignoreNextLineCommentLowerCase &&
75 // It’s not the first line that can’t be ignored, because there can’t be
76 // an ignore comment on the previous line.
77 lineIndex &&
78 // The previous line contains the case-insensitive comment to ignore
79 // this line.
80 lines[lineIndex - 1]
81 .trim()
82 .toLowerCase()
83 .endsWith(ignoreNextLineCommentLowerCase)
84 )
85 ignore = true;
86 }
87
88 if (endOffset >= lineOffset && endOffset < nextLineOffset) {
89 endLine = lineIndex + 1;
90 endColumn = endOffset - lineOffset + 1;
91 break;
92 }
93
94 lineOffset = nextLineOffset;
95 }
96
97 return {
98 ignore,
99 start: {
100 offset: startOffset,
101 line: /** @type {number} */ (startLine),
102 column: /** @type {number} */ (startColumn),
103 },
104 end: {
105 offset: endOffset,
106 line: /** @type {number} */ (endLine),
107 column: /** @type {number} */ (endColumn),
108 },
109 };
110}
111
112/**
113 * Source code location.
114 * @typedef {object} SourceCodeLocation
115 * @prop {number} offset Character offset.
116 * @prop {number} line Line number.
117 * @prop {number} column Column number.
118 */
119
120/**
121 * Source code range details.
122 * @typedef {object} SourceCodeRange
123 * @prop {boolean} ignore Should it be ignored.
124 * @prop {SourceCodeLocation} start Start location.
125 * @prop {SourceCodeLocation} end End location.
126 */