8000 perf: improve time complexity of `getLocFromIndex` (#19782) · eslint/eslint@e392895 · GitHub
[go: up one dir, main page]

Skip to content

Commit e392895

Browse files
authored
perf: improve time complexity of getLocFromIndex (#19782)
* perf: improve `getLocFromIndex` and `getIndexFromLoc` * wip: binary search * wip: caching * wip: use bitwise OR * wip: remove cache
1 parent 1ba3318 commit e392895

File tree

2 files changed

+41
-6
lines changed

2 files changed

+41
-6
lines changed

lib/languages/js/source-code/source-code.js

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,33 @@ function isSpaceBetween(sourceCode, first, second, checkInsideOfJSXText) {
240240
return false;
241241
}
242242

243+
/**
244+
* Performs binary search to find the line number containing a given character index.
245+
* Returns the lower bound - the index of the first element greater than the target.
246+
* **Please note that the `lineStartIndices` should be sorted in ascending order**.
247+
* - Time Complexity: O(log n) - Significantly faster than linear search for large files.
248+
* @param {number[]} lineStartIndices Sorted array of line start indices.
249+
* @param {number} target The character index to find the line number for.
250+
* @returns {number} The 1-based line number for the target index.
251+
* @private
252+
*/
253+
function findLineNumberBinarySearch(lineStartIndices, target) {
254+
let low = 0;
255+
let high = lineStartIndices.length;
256+
257+
while (low < high) {
258+
const mid = ((low + high) / 2) | 0; // Use bitwise OR to floor the division
259+
260+
if (target < lineStartIndices[mid]) {
261+
high = mid;
262+
} else {
263+
low = mid + 1;
264+
}
265+
}
266+
267+
return low;
268+
}
269+
243270
//-----------------------------------------------------------------------------
244271
// Directive Comments
245272
//-----------------------------------------------------------------------------
@@ -721,9 +748,9 @@ class SourceCode extends TokenStore {
721748

722749
/**
723750
* Converts a source text index into a (line, column) pair.
724-
* @param {number} index The index of a character in a file
751+
* @param {number} index The index of a character in a file.
725752
* @throws {TypeError|RangeError} If non-numeric index or index out of range.
726-
* @returns {{line: number, column: number}} A {line, column} location object with a 0-indexed column
753+
* @returns {{line: number, column: number}} A {line, column} location object with 1-indexed line and 0-indexed column.
727754
* @public
728755
*/
729756
getLocFromIndex(index) {
@@ -758,7 +785,7 @@ class SourceCode extends TokenStore {
758785
const lineNumber =
759786
index >= this.lineStartIndices.at(-1)
760787
? this.lineStartIndices.length
761-
: this.lineStartIndices.findIndex(el => index < el);
788+
: findLineNumberBinarySearch(this.lineStartIndices, index);
762789

763790
return {
764791
line: lineNumber,

tests/lib/languages/js/source-code/source-code.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2205,9 +2205,9 @@ describe("SourceCode", () => {
22052205
});
22062206

22072207
it("should return the location of a range index", () => {
2208-
assert.deepStrictEqual(sourceCode.getLocFromIndex(5), {
2209-
line: 2,
2210-
column: 1,
2208+
assert.deepStrictEqual(sourceCode.getLocFromIndex(0), {
2209+
line: 1,
2210+
column: 0,
22112211
});
22122212
assert.deepStrictEqual(sourceCode.getLocFromIndex(3), {
22132213
line: 1,
@@ -2217,6 +2217,14 @@ describe("SourceCode", () => {
22172217
line: 2,
22182218
column: 0,
22192219
});
2220+
assert.deepStrictEqual(sourceCode.getLocFromIndex(5), {
2221+
line: 2,
2222+
column: 1,
2223+
});
2224+
assert.deepStrictEqual(sourceCode.getLocFromIndex(15), {
2225+
line: 4,
2226+
column: 2,
2227+
});
22202228
assert.deepStrictEqual(sourceCode.getLocFromIndex(21), {
22212229
line: 6,
22222230
column: 0,

0 commit comments

Comments
 (0)
0