From 6c24a84cc0622016c155cefe69a793ac03f1560d Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Fri, 13 Mar 2026 00:25:33 +0000 Subject: [PATCH] refactor(linter/plugins): reuse shared binary search function for comments methods (#20311) Continuation of #20310. Use the same binary search function `firstTokenAtOrAfter` (introduced in that PR) for comments methods. --- apps/oxlint/src-js/plugins/comments.ts | 86 +++++--------------- apps/oxlint/src-js/plugins/tokens_methods.ts | 6 +- 2 files changed, 25 insertions(+), 67 deletions(-) diff --git a/apps/oxlint/src-js/plugins/comments.ts b/apps/oxlint/src-js/plugins/comments.ts index 6d75601fd0958..26fdf106ad620 100644 --- a/apps/oxlint/src-js/plugins/comments.ts +++ b/apps/oxlint/src-js/plugins/comments.ts @@ -3,6 +3,7 @@ */ import { ast, initAst, sourceText } from "./source_code.ts"; +import { firstTokenAtOrAfter } from "./tokens_methods.ts"; import { debugAssertIsNonNull } from "../utils/asserts.ts"; import type { Comment, Node, NodeOrToken } from "./types.ts"; @@ -44,29 +45,20 @@ export function getCommentsBefore(nodeOrToken: NodeOrToken): Comment[] { debugAssertIsNonNull(ast); debugAssertIsNonNull(sourceText); - const { comments } = ast, - commentsLength = comments.length; + const { comments } = ast; let targetStart = nodeOrToken.range[0]; // start - let sliceStart = commentsLength; - let sliceEnd = 0; - - // Binary search for the comment immediately before `nodeOrToken`. - for (let lo = 0, hi = commentsLength; lo < hi; ) { - const mid = (lo + hi) >> 1; - if (comments[mid].end <= targetStart) { - sliceEnd = lo = mid + 1; - } else { - hi = mid; - } - } + // Binary search for first comment at or past `nodeOrToken`'s start. + // Comments before this index are candidates to be included in returned array. + const sliceEnd = firstTokenAtOrAfter(comments, targetStart, 0); + let sliceStart = comments.length; for (let i = sliceEnd - 1; i >= 0; i--) { const comment = comments[i]; const gap = sourceText.slice(comment.end, targetStart); // Ensure that there is nothing except whitespace between the end of the - // current comment and the start of the next as we iterate backwards. + // current comment and the start of the next one as we iterate backwards if (WHITESPACE_ONLY_REGEXP.test(gap)) { sliceStart = i; targetStart = comment.start; @@ -101,27 +93,19 @@ export function getCommentsAfter(nodeOrToken: NodeOrToken): Comment[] { debugAssertIsNonNull(ast); debugAssertIsNonNull(sourceText); - const { comments } = ast, - commentsLength = comments.length; + const { comments } = ast; let targetEnd = nodeOrToken.range[1]; // end - let sliceStart = commentsLength; - let sliceEnd = 0; - - // Binary search for the comment immediately after `nodeOrToken`. - for (let lo = 0, hi = commentsLength; lo < hi; ) { - const mid = (lo + hi) >> 1; - if (comments[mid].start < targetEnd) { - lo = mid + 1; - } else { - sliceStart = hi = mid; - } - } + // Binary search for first comment at or past `nodeOrToken`'s end. + // Comments from this index onwards are candidates to be included in returned array. + const sliceStart = firstTokenAtOrAfter(comments, targetEnd, 0); + const commentsLength = comments.length; + let sliceEnd = 0; for (let i = sliceStart; i < commentsLength; i++) { // Ensure that there is nothing except whitespace between the - // end of the previous comment and the start of the current. + // end of the previous comment and the start of the current one const comment = comments[i]; const gap = sourceText.slice(targetEnd, comment.start); if (WHITESPACE_ONLY_REGEXP.test(gap)) { @@ -144,36 +128,17 @@ export function getCommentsInside(node: Node): Comment[] { if (ast === null) initAst(); debugAssertIsNonNull(ast); - const { comments } = ast, - commentsLength = comments.length; - - let sliceStart = commentsLength; - let sliceEnd: number | undefined = undefined; + const { comments } = ast; const { range } = node, rangeStart = range[0], rangeEnd = range[1]; - // Binary search for first comment within `node`'s range. - for (let lo = 0, hi = commentsLength; lo < hi; ) { - const mid = (lo + hi) >> 1; - if (comments[mid].start < rangeStart) { - lo = mid + 1; - } else { - sliceStart = hi = mid; - } - } - + // Binary search for first comment within `node`'s range + const sliceStart = firstTokenAtOrAfter(comments, rangeStart, 0); // Binary search for first comment outside `node`'s range. // Its index is used as `sliceEnd`, which is exclusive of the slice. - for (let lo = sliceStart, hi = commentsLength; lo < hi; ) { - const mid = (lo + hi) >> 1; - if (comments[mid].start < rangeEnd) { - lo = mid + 1; - } else { - sliceEnd = hi = mid; - } - } + const sliceEnd = firstTokenAtOrAfter(comments, rangeEnd, sliceStart); return comments.slice(sliceStart, sliceEnd); } @@ -193,22 +158,11 @@ export function commentsExistBetween( // Find the first comment after `nodeOrToken1` ends. const { comments } = ast, - commentsLength = comments.length, betweenRangeStart = nodeOrToken1.range[1]; - let firstCommentBetween = -1; - - for (let lo = 0, hi = commentsLength; lo < hi; ) { - const mid = (lo + hi) >> 1; - if (comments[mid].start < betweenRangeStart) { - lo = mid + 1; - } else { - firstCommentBetween = hi = mid; - } - } + const firstCommentBetween = firstTokenAtOrAfter(comments, betweenRangeStart, 0); // Check if it ends before `nodeOrToken2` starts. return ( - 0 <= firstCommentBetween && - firstCommentBetween < commentsLength && + firstCommentBetween < comments.length && comments[firstCommentBetween].end <= nodeOrToken2.range[0] ); } diff --git a/apps/oxlint/src-js/plugins/tokens_methods.ts b/apps/oxlint/src-js/plugins/tokens_methods.ts index 75ae5606cd9d2..2f4a88e056efb 100644 --- a/apps/oxlint/src-js/plugins/tokens_methods.ts +++ b/apps/oxlint/src-js/plugins/tokens_methods.ts @@ -1488,7 +1488,11 @@ export function isSpaceBetweenTokens(first: NodeOrToken, second: NodeOrToken): b * @param startIndex - Starting index for the search * @returns Index of first token with `start >= offset` */ -function firstTokenAtOrAfter(tokens: TokenOrComment[], offset: number, startIndex: number): number { +export function firstTokenAtOrAfter( + tokens: TokenOrComment[], + offset: number, + startIndex: number, +): number { for (let endIndex = tokens.length; startIndex < endIndex; ) { const mid = (startIndex + endIndex) >> 1; if (tokens[mid].start < offset) {