diff --git a/apps/oxlint/src-js/plugins/tokens.ts b/apps/oxlint/src-js/plugins/tokens.ts index 9043577317efd..17d25cc1e25d9 100644 --- a/apps/oxlint/src-js/plugins/tokens.ts +++ b/apps/oxlint/src-js/plugins/tokens.ts @@ -331,8 +331,7 @@ export function getTokens( debugAssertIsNonNull(comments); // Maximum number of tokens to return - const count = - typeof countOptions === "object" && countOptions !== null ? countOptions.count : null; + let count = typeof countOptions === "object" && countOptions !== null ? countOptions.count : null; // Number of preceding tokens to additionally return const beforeCount = typeof countOptions === "number" ? countOptions : 0; @@ -360,36 +359,36 @@ export function getTokens( countOptions.includeComments; // Source array of tokens to search in - let nodeTokens: Token[] | null = null; + let tokenList: Token[] | null = null; if (includeComments) { if (tokensWithComments === null) initTokensWithComments(); debugAssertIsNonNull(tokensWithComments); - nodeTokens = tokensWithComments; + tokenList = tokensWithComments; } else { - nodeTokens = tokens; + tokenList = tokens; } const { range } = node, rangeStart = range[0], rangeEnd = range[1]; - // Binary search for first token within `node`'s range - const tokensLength = nodeTokens.length; + // Binary search for first token past `node`'s start + const tokensLength = tokenList.length; let sliceStart = tokensLength; for (let lo = 0; lo < sliceStart; ) { const mid = (lo + sliceStart) >> 1; - if (nodeTokens[mid].range[0] < rangeStart) { + if (tokenList[mid].range[0] < rangeStart) { lo = mid + 1; } else { sliceStart = mid; } } - // Binary search for the first token outside `node`'s range + // Binary search for first token past `node`'s end let sliceEnd = tokensLength; for (let lo = sliceStart; lo < sliceEnd; ) { const mid = (lo + sliceEnd) >> 1; - if (nodeTokens[mid].range[0] < rangeEnd) { + if (tokenList[mid].range[0] < rangeEnd) { lo = mid + 1; } else { sliceEnd = mid; @@ -397,16 +396,31 @@ export function getTokens( } sliceStart = max(0, sliceStart - beforeCount); - sliceEnd += afterCount; + sliceEnd = min(sliceEnd + afterCount, tokensLength); - nodeTokens = nodeTokens.slice(sliceStart, sliceEnd); + if (typeof filter !== "function") { + return tokenList.slice(sliceStart, min(sliceStart + (count ?? sliceEnd), sliceEnd)); + } - // Filter before limiting by `count` - if (filter) nodeTokens = nodeTokens.filter(filter); - if (typeof count === "number" && count < nodeTokens.length) - nodeTokens = nodeTokens.slice(0, count); + const allTokens: Token[] = []; - return nodeTokens; + if (typeof count !== "number") { + for (let i = sliceStart; i < sliceEnd; i++) { + const token = tokenList[i]; + if (filter(token)) allTokens.push(token); + } + return allTokens; + } + + for (let i = sliceStart; i < sliceEnd && count > 0; i++) { + const token = tokenList[i]; + if (filter(token)) { + allTokens.push(token); + count--; + } + } + + return allTokens; } /** @@ -425,7 +439,7 @@ export function getFirstToken( debugAssertIsNonNull(tokens); debugAssertIsNonNull(comments); - // Number of tokens to skip + // Number of tokens at the beginning of the given node to skip let skip = typeof skipOptions === "number" ? skipOptions @@ -449,25 +463,25 @@ export function getFirstToken( skipOptions.includeComments; // Source array of tokens - let nodeTokens: Token[] | null = null; + let tokenList: Token[] | null = null; if (includeComments) { if (tokensWithComments === null) initTokensWithComments(); debugAssertIsNonNull(tokensWithComments); - nodeTokens = tokensWithComments; + tokenList = tokensWithComments; } else { - nodeTokens = tokens; + tokenList = tokens; } const { range } = node, rangeStart = range[0], rangeEnd = range[1]; - // Binary search for first token within `node`'s range - const tokensLength = nodeTokens.length; + // Binary search for first token past `node`'s start + const tokensLength = tokenList.length; let startIndex = tokensLength; for (let lo = 0; lo < startIndex; ) { const mid = (lo + startIndex) >> 1; - if (nodeTokens[mid].range[0] < rangeStart) { + if (tokenList[mid].range[0] < rangeStart) { lo = mid + 1; } else { startIndex = mid; @@ -475,32 +489,27 @@ export function getFirstToken( } if (typeof filter !== "function") { - if (typeof skip !== "number") { - // If no tokens start at or after `rangeStart`, return `null` - if (startIndex >= tokensLength) return null; - // Check if the first candidate token is actually within the range - if (nodeTokens[startIndex].range[0] >= rangeEnd) return null; - return nodeTokens[startIndex]; - } + const skipTo = startIndex + (skip ?? 0); + // Avoid indexing out of bounds + if (skipTo >= tokensLength) return null; - const firstTokenAfterSkip = nodeTokens[startIndex + skip]; - if (firstTokenAfterSkip === undefined) return null; - if (firstTokenAfterSkip.range[0] >= rangeEnd) return null; - return firstTokenAfterSkip; + const token = tokenList[skipTo]; + if (token.range[0] >= rangeEnd) return null; + return token; } if (typeof skip !== "number") { for (let i = startIndex; i < tokensLength; i++) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (token.range[0] >= rangeEnd) return null; // Token is outside the node if (filter(token)) return token; } } else { for (let i = startIndex; i < tokensLength; i++) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (token.range[0] >= rangeEnd) return null; // Token is outside the node if (filter(token)) { - if (skip === 0) return token; + if (skip <= 0) return token; skip--; } } @@ -545,36 +554,36 @@ export function getFirstTokens( "includeComments" in countOptions && countOptions.includeComments; - let nodeTokens: Token[] | null = null; + let tokenList: Token[] | null = null; if (includeComments) { if (tokensWithComments === null) initTokensWithComments(); debugAssertIsNonNull(tokensWithComments); - nodeTokens = tokensWithComments; + tokenList = tokensWithComments; } else { - nodeTokens = tokens; + tokenList = tokens; } const { range } = node, rangeStart = range[0], rangeEnd = range[1]; - // Binary search for first token within `node`'s range - const tokensLength = nodeTokens.length; + // Binary search for first token past `node`'s start + const tokensLength = tokenList.length; let sliceStart = tokensLength; for (let lo = 0; lo < sliceStart; ) { const mid = (lo + sliceStart) >> 1; - if (nodeTokens[mid].range[0] < rangeStart) { + if (tokenList[mid].range[0] < rangeStart) { lo = mid + 1; } else { sliceStart = mid; } } - // Binary search for the first token outside `node`'s range + // Binary search for first token past `node`'s end let sliceEnd = tokensLength; for (let lo = sliceStart; lo < sliceEnd; ) { const mid = (lo + sliceEnd) >> 1; - if (nodeTokens[mid].range[0] < rangeEnd) { + if (tokenList[mid].range[0] < rangeEnd) { lo = mid + 1; } else { sliceEnd = mid; @@ -582,19 +591,19 @@ export function getFirstTokens( } if (typeof filter !== "function") { - if (typeof count !== "number") return nodeTokens.slice(sliceStart, sliceEnd); - return nodeTokens.slice(sliceStart, min(sliceStart + count, sliceEnd)); + if (typeof count !== "number") return tokenList.slice(sliceStart, sliceEnd); + return tokenList.slice(sliceStart, min(sliceStart + count, sliceEnd)); } const firstTokens: Token[] = []; if (typeof count !== "number") { for (let i = sliceStart; i < sliceEnd; i++) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (filter(token)) firstTokens.push(token); } } else { for (let i = sliceStart; i < sliceEnd && firstTokens.length < count; i++) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (filter(token)) firstTokens.push(token); } } @@ -617,7 +626,7 @@ export function getLastToken( debugAssertIsNonNull(tokens); debugAssertIsNonNull(comments); - // Number of tokens to skip from the end + // Number of tokens at the end of the given node to skip let skip = typeof skipOptions === "number" ? skipOptions @@ -640,25 +649,25 @@ export function getLastToken( skipOptions.includeComments; // Source array of tokens to search in - let nodeTokens: Token[] | null = null; + let tokenList: Token[] | null = null; if (includeComments) { if (tokensWithComments === null) initTokensWithComments(); debugAssertIsNonNull(tokensWithComments); - nodeTokens = tokensWithComments; + tokenList = tokensWithComments; } else { - nodeTokens = tokens; + tokenList = tokens; } const { range } = node, rangeStart = range[0], rangeEnd = range[1]; - // Binary search for the last token within `node`'s range - const nodeTokensLength = nodeTokens.length; + // Binary search for token immediately before `node`'s end + const tokensLength = tokenList.length; let lastTokenIndex = 0; - for (let hi = nodeTokensLength; lastTokenIndex < hi; ) { + for (let hi = tokensLength; lastTokenIndex < hi; ) { const mid = (lastTokenIndex + hi) >> 1; - if (nodeTokens[mid].range[0] < rangeEnd) { + if (tokenList[mid].range[0] < rangeEnd) { lastTokenIndex = mid + 1; } else { hi = mid; @@ -668,25 +677,26 @@ export function getLastToken( lastTokenIndex--; if (typeof filter !== "function") { - if (typeof skip !== "number") return nodeTokens[lastTokenIndex] ?? null; - - const token = nodeTokens[lastTokenIndex - skip]; - if (token === undefined || token.range[0] < rangeStart) return null; + const skipTo = lastTokenIndex - (skip ?? 0); + // Avoid indexing out of bounds + if (skipTo < 0) return null; + const token = tokenList[skipTo]; + if (token.range[0] < rangeStart) return null; return token; } if (typeof skip !== "number") { for (let i = lastTokenIndex; i >= 0; i--) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (token.range[0] < rangeStart) return null; if (filter(token)) return token; } } else { for (let i = lastTokenIndex; i >= 0; i--) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (token.range[0] < rangeStart) return null; if (filter(token)) { - if (skip === 0) return token; + if (skip <= 0) return token; skip--; } } @@ -735,36 +745,36 @@ export function getLastTokens( countOptions.includeComments; // Source array of tokens to search in - let nodeTokens: Token[] | null = null; + let tokenList: Token[] | null = null; if (includeComments) { if (tokensWithComments === null) initTokensWithComments(); debugAssertIsNonNull(tokensWithComments); - nodeTokens = tokensWithComments; + tokenList = tokensWithComments; } else { - nodeTokens = tokens; + tokenList = tokens; } const { range } = node, rangeStart = range[0], rangeEnd = range[1]; - // Binary search for first token within `node`'s range - const tokensLength = nodeTokens.length; + // Binary search for first token past `node`'s start + const tokensLength = tokenList.length; let sliceStart = tokensLength; for (let lo = 0; lo < sliceStart; ) { const mid = (lo + sliceStart) >> 1; - if (nodeTokens[mid].range[0] < rangeStart) { + if (tokenList[mid].range[0] < rangeStart) { lo = mid + 1; } else { sliceStart = mid; } } - // Binary search for the first token outside `node`'s range + // Binary search for first token past `node`'s end let sliceEnd = tokensLength; for (let lo = sliceStart; lo < sliceEnd; ) { const mid = (lo + sliceEnd) >> 1; - if (nodeTokens[mid].range[0] < rangeEnd) { + if (tokenList[mid].range[0] < rangeEnd) { lo = mid + 1; } else { sliceEnd = mid; @@ -772,20 +782,20 @@ export function getLastTokens( } if (typeof filter !== "function") { - if (typeof count !== "number") return nodeTokens.slice(sliceStart, sliceEnd); - return nodeTokens.slice(max(sliceStart, sliceEnd - count), sliceEnd); + if (typeof count !== "number") return tokenList.slice(sliceStart, sliceEnd); + return tokenList.slice(max(sliceStart, sliceEnd - count), sliceEnd); } const lastTokens: Token[] = []; if (typeof count !== "number") { for (let i = sliceStart; i < sliceEnd; i++) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (filter(token)) lastTokens.push(token); } } else { // `count` is the number of tokens within range from the end, so we iterate in reverse for (let i = sliceEnd - 1; i >= sliceStart && lastTokens.length < count; i--) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (filter(token)) lastTokens.unshift(token); } } @@ -831,24 +841,24 @@ export function getTokenBefore( skipOptions.includeComments; // Source array of tokens to search in - let nodeTokens: Token[] | null = null; + let tokenList: Token[] | null = null; if (includeComments) { if (tokensWithComments === null) initTokensWithComments(); debugAssertIsNonNull(tokensWithComments); - nodeTokens = tokensWithComments; + tokenList = tokensWithComments; } else { - nodeTokens = tokens; + tokenList = tokens; } const nodeStart = nodeOrToken.range[0]; // Index of the token immediately before the given node, token, or comment. let beforeIndex = 0; - let hi = nodeTokens.length; + let hi = tokenList.length; while (beforeIndex < hi) { const mid = (beforeIndex + hi) >> 1; - if (nodeTokens[mid].range[0] < nodeStart) { + if (tokenList[mid].range[0] < nodeStart) { beforeIndex = mid + 1; } else { hi = mid; @@ -858,21 +868,23 @@ export function getTokenBefore( beforeIndex--; if (typeof filter !== "function") { - if (typeof skip !== "number") return nodeTokens[beforeIndex] ?? null; - return nodeTokens[beforeIndex - skip] ?? null; + const skipTo = beforeIndex - (skip ?? 0); + // Avoid indexing out of bounds + if (skipTo < 0) return null; + return tokenList[skipTo]; } if (typeof skip !== "number") { while (beforeIndex >= 0) { - const token = nodeTokens[beforeIndex]; + const token = tokenList[beforeIndex]; if (filter(token)) return token; beforeIndex--; } } else { while (beforeIndex >= 0) { - const token = nodeTokens[beforeIndex]; + const token = tokenList[beforeIndex]; if (filter(token)) { - if (skip === 0) return token; + if (skip <= 0) return token; skip--; } beforeIndex--; @@ -941,22 +953,22 @@ export function getTokensBefore( countOptions.includeComments; // Source array of tokens to search in - let nodeTokens: Token[] | null = null; + let tokenList: Token[] | null = null; if (includeComments) { if (tokensWithComments === null) initTokensWithComments(); debugAssertIsNonNull(tokensWithComments); - nodeTokens = tokensWithComments; + tokenList = tokensWithComments; } else { - nodeTokens = tokens; + tokenList = tokens; } const targetStart = nodeOrToken.range[0]; let sliceEnd = 0; - let hi = nodeTokens.length; + let hi = tokenList.length; while (sliceEnd < hi) { const mid = (sliceEnd + hi) >> 1; - if (nodeTokens[mid].range[0] < targetStart) { + if (tokenList[mid].range[0] < targetStart) { sliceEnd = mid + 1; } else { hi = mid; @@ -965,20 +977,20 @@ export function getTokensBefore( // Fast path for the common case if (typeof filter !== "function") { - if (typeof count !== "number") return nodeTokens.slice(0, sliceEnd); - return nodeTokens.slice(sliceEnd - count, sliceEnd); + if (typeof count !== "number") return tokenList.slice(0, sliceEnd); + return tokenList.slice(sliceEnd - count, sliceEnd); } const tokensBefore: Token[] = []; if (typeof count !== "number") { for (let i = 0; i < sliceEnd; i++) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (filter(token)) tokensBefore.push(token); } } else { // Count is the number of preceding tokens, so we iterate in reverse for (let i = sliceEnd - 1; i >= 0 && tokensBefore.length < count; i--) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (filter(token)) tokensBefore.unshift(token); } } @@ -1001,12 +1013,13 @@ export function getTokenAfter( debugAssertIsNonNull(tokens); debugAssertIsNonNull(comments); + // Number of tokens following the given node to skip let skip = typeof skipOptions === "number" ? skipOptions : typeof skipOptions === "object" && skipOptions !== null - ? (skipOptions.skip ?? 0) - : 0; + ? skipOptions.skip + : null; const filter = typeof skipOptions === "function" @@ -1022,24 +1035,24 @@ export function getTokenAfter( skipOptions.includeComments; // Source array of tokens to search in - let nodeTokens: Token[] | null = null; + let tokenList: Token[] | null = null; if (includeComments) { if (tokensWithComments === null) initTokensWithComments(); debugAssertIsNonNull(tokensWithComments); - nodeTokens = tokensWithComments; + tokenList = tokensWithComments; } else { - nodeTokens = tokens; + tokenList = tokens; } const { range } = nodeOrToken, rangeEnd = range[1]; - // Binary search for the first token that starts at or after the end of the node/token - const tokensLength = nodeTokens.length; + // Binary search for first token past `nodeOrToken`'s end + const tokensLength = tokenList.length; let startIndex = tokensLength; for (let lo = 0; lo < startIndex; ) { const mid = (lo + startIndex) >> 1; - if (nodeTokens[mid].range[0] < rangeEnd) { + if (tokenList[mid].range[0] < rangeEnd) { lo = mid + 1; } else { startIndex = mid; @@ -1048,20 +1061,22 @@ export function getTokenAfter( // Fast path for the common case if (typeof filter !== "function") { - if (typeof skip !== "number") return nodeTokens[startIndex] ?? null; - return nodeTokens[startIndex + skip] ?? null; + const skipTo = startIndex + (skip ?? 0); + // Avoid indexing out of bounds + if (skipTo >= tokensLength) return null; + return tokenList[skipTo]; } if (typeof skip !== "number") { for (let i = startIndex; i < tokensLength; i++) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (filter(token)) return token; } } else { for (let i = startIndex; i < tokensLength; i++) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (filter(token)) { - if (skip === 0) return token; + if (skip <= 0) return token; skip--; } } @@ -1125,21 +1140,21 @@ export function getTokensAfter( "includeComments" in countOptions && countOptions.includeComments; - let nodeTokens: Token[]; + let tokenList: Token[]; if (includeComments) { if (tokensWithComments === null) initTokensWithComments(); debugAssertIsNonNull(tokensWithComments); - nodeTokens = tokensWithComments; + tokenList = tokensWithComments; } else { - nodeTokens = tokens; + tokenList = tokens; } const rangeEnd = nodeOrToken.range[1]; - let sliceStart = nodeTokens.length; + let sliceStart = tokenList.length; for (let lo = 0; lo < sliceStart; ) { const mid = (lo + sliceStart) >> 1; - if (nodeTokens[mid].range[0] < rangeEnd) { + if (tokenList[mid].range[0] < rangeEnd) { lo = mid + 1; } else { sliceStart = mid; @@ -1148,23 +1163,23 @@ export function getTokensAfter( // Fast path for the common case if (typeof filter !== "function") { - if (typeof count !== "number") return nodeTokens.slice(sliceStart); - return nodeTokens.slice(sliceStart, sliceStart + count); + if (typeof count !== "number") return tokenList.slice(sliceStart); + return tokenList.slice(sliceStart, sliceStart + count); } - const nodeTokensAfter: Token[] = []; + const tokenListAfter: Token[] = []; if (typeof count !== "number") { - for (let i = sliceStart; i < nodeTokens.length; i++) { - const token = nodeTokens[i]; - if (filter(token)) nodeTokensAfter.push(token); + for (let i = sliceStart; i < tokenList.length; i++) { + const token = tokenList[i]; + if (filter(token)) tokenListAfter.push(token); } } else { - for (let i = sliceStart; i < nodeTokens.length && nodeTokensAfter.length < count; i++) { - const token = nodeTokens[i]; - if (filter(token)) nodeTokensAfter.push(token); + for (let i = sliceStart; i < tokenList.length && tokenListAfter.length < count; i++) { + const token = tokenList[i]; + if (filter(token)) tokenListAfter.push(token); } } - return nodeTokensAfter; + return tokenListAfter; } /** @@ -1208,13 +1223,13 @@ export function getTokensBetween( "includeComments" in countOptions && countOptions.includeComments; - let nodeTokens: Token[]; + let tokenList: Token[]; if (includeComments) { if (tokensWithComments === null) initTokensWithComments(); debugAssertIsNonNull(tokensWithComments); - nodeTokens = tokensWithComments; + tokenList = tokensWithComments; } else { - nodeTokens = tokens; + tokenList = tokens; } // This range is not invariant over node order. @@ -1222,24 +1237,24 @@ export function getTokensBetween( // Same as ESLint's implementation. const rangeStart = left.range[1], rangeEnd = right.range[0], - tokensLength = nodeTokens.length; + tokensLength = tokenList.length; - // Binary search for first token past the beginning of the `between` range + // Binary search for first token past "between" range start let sliceStart = tokensLength; for (let lo = 0; lo < sliceStart; ) { const mid = (lo + sliceStart) >> 1; - if (nodeTokens[mid].range[0] < rangeStart) { + if (tokenList[mid].range[0] < rangeStart) { lo = mid + 1; } else { sliceStart = mid; } } - // Binary search for first token past the end of the `between` range + // Binary search for first token past "between" range end let sliceEnd = tokensLength; for (let lo = sliceStart; lo < sliceEnd; ) { const mid = (lo + sliceEnd) >> 1; - if (nodeTokens[mid].range[0] < rangeEnd) { + if (tokenList[mid].range[0] < rangeEnd) { lo = mid + 1; } else { sliceEnd = mid; @@ -1251,19 +1266,19 @@ export function getTokensBetween( sliceEnd += padding; if (typeof filter !== "function") { - if (typeof count !== "number") return nodeTokens.slice(sliceStart, sliceEnd); - return nodeTokens.slice(sliceStart, min(sliceStart + count, sliceEnd)); + if (typeof count !== "number") return tokenList.slice(sliceStart, sliceEnd); + return tokenList.slice(sliceStart, min(sliceStart + count, sliceEnd)); } const tokensBetween: Token[] = []; if (typeof count !== "number") { for (let i = sliceStart; i < sliceEnd; i++) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (filter(token)) tokensBetween.push(token); } } else { for (let i = sliceStart; i < sliceEnd && tokensBetween.length < count; i++) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (filter(token)) tokensBetween.push(token); } } @@ -1288,6 +1303,7 @@ export function getFirstTokenBetween( debugAssertIsNonNull(tokens); debugAssertIsNonNull(comments); + // Number of tokens at the beginning of the "between" range to skip let skip = typeof skipOptions === "number" ? skipOptions @@ -1308,13 +1324,13 @@ export function getFirstTokenBetween( "includeComments" in skipOptions && skipOptions.includeComments; - let nodeTokens: Token[] | null = null; + let tokenList: Token[] | null = null; if (includeComments) { if (tokensWithComments === null) initTokensWithComments(); debugAssertIsNonNull(tokensWithComments); - nodeTokens = tokensWithComments; + tokenList = tokensWithComments; } else { - nodeTokens = tokens; + tokenList = tokens; } // This range is not invariant over node order. @@ -1323,13 +1339,13 @@ export function getFirstTokenBetween( const rangeStart = left.range[1], rangeEnd = right.range[0]; - const tokensLength = nodeTokens.length; + const tokensLength = tokenList.length; - // Binary search for the token following the left node + // Binary search for token immediately following `left` let firstTokenIndex = tokensLength; for (let lo = 0; lo < firstTokenIndex; ) { const mid = (lo + firstTokenIndex) >> 1; - if (nodeTokens[mid].range[0] < rangeStart) { + if (tokenList[mid].range[0] < rangeStart) { lo = mid + 1; } else { firstTokenIndex = mid; @@ -1337,20 +1353,23 @@ export function getFirstTokenBetween( } if (typeof filter !== "function") { - const token = nodeTokens[typeof skip === "number" ? firstTokenIndex + skip : firstTokenIndex]; - if (token === undefined || token.range[0] >= rangeEnd) return null; + const skipTo = firstTokenIndex + (skip ?? 0); + // Avoid indexing out of bounds + if (skipTo >= tokensLength) return null; + const token = tokenList[skipTo]; + if (token.range[0] >= rangeEnd) return null; return token; } if (typeof skip !== "number") { for (let i = firstTokenIndex; i < tokensLength; i++) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (token.range[0] >= rangeEnd) return null; if (filter(token)) return token; } } else { for (let i = firstTokenIndex; i < tokensLength; i++) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (token.range[0] >= rangeEnd) return null; if (filter(token)) { if (skip <= 0) return token; @@ -1400,13 +1419,13 @@ export function getFirstTokensBetween( "includeComments" in countOptions && countOptions.includeComments; - let nodeTokens: Token[] | null = null; + let tokenList: Token[] | null = null; if (includeComments) { if (tokensWithComments === null) initTokensWithComments(); debugAssertIsNonNull(tokensWithComments); - nodeTokens = tokensWithComments; + tokenList = tokensWithComments; } else { - nodeTokens = tokens; + tokenList = tokens; } // This range is not invariant over node order. @@ -1415,13 +1434,13 @@ export function getFirstTokensBetween( const rangeStart = left.range[1], rangeEnd = right.range[0]; - const tokensLength = nodeTokens.length; + const tokensLength = tokenList.length; // Find the first token after `left` let sliceStart = tokensLength; for (let lo = 0; lo < sliceStart; ) { const mid = (lo + sliceStart) >> 1; - if (nodeTokens[mid].range[0] < rangeStart) { + if (tokenList[mid].range[0] < rangeStart) { lo = mid + 1; } else { sliceStart = mid; @@ -1432,7 +1451,7 @@ export function getFirstTokensBetween( let sliceEnd = tokensLength; for (let lo = sliceStart; lo < sliceEnd; ) { const mid = (lo + sliceEnd) >> 1; - if (nodeTokens[mid].range[0] < rangeEnd) { + if (tokenList[mid].range[0] < rangeEnd) { lo = mid + 1; } else { sliceEnd = mid; @@ -1440,19 +1459,19 @@ export function getFirstTokensBetween( } if (typeof filter !== "function") { - if (typeof count !== "number") return nodeTokens.slice(sliceStart, sliceEnd); - return nodeTokens.slice(sliceStart, min(sliceStart + count, sliceEnd)); + if (typeof count !== "number") return tokenList.slice(sliceStart, sliceEnd); + return tokenList.slice(sliceStart, min(sliceStart + count, sliceEnd)); } const firstTokens: Token[] = []; if (typeof count !== "number") { for (let i = sliceStart; i < sliceEnd; i++) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (filter(token)) firstTokens.push(token); } } else { for (let i = sliceStart; i < sliceEnd && firstTokens.length < count; i++) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (filter(token)) firstTokens.push(token); } } @@ -1477,6 +1496,7 @@ export function getLastTokenBetween( debugAssertIsNonNull(tokens); debugAssertIsNonNull(comments); + // Number of tokens at the end of the "between" range to skip let skip = typeof skipOptions === "number" ? skipOptions @@ -1497,13 +1517,13 @@ export function getLastTokenBetween( "includeComments" in skipOptions && skipOptions.includeComments; - let nodeTokens: Token[] | null = null; + let tokenList: Token[] | null = null; if (includeComments) { if (tokensWithComments === null) initTokensWithComments(); debugAssertIsNonNull(tokensWithComments); - nodeTokens = tokensWithComments; + tokenList = tokensWithComments; } else { - nodeTokens = tokens; + tokenList = tokens; } // This range is not invariant over node order. @@ -1512,12 +1532,12 @@ export function getLastTokenBetween( const rangeStart = left.range[1], rangeEnd = right.range[0]; - // Binary search for the token preceding the right node. + // Binary search for token immediately preceding `right` // The found token may be within the left node if there are no tokens between the nodes. let lastTokenIndex = -1; - for (let lo = 0, hi = nodeTokens.length - 1; lo <= hi; ) { + for (let lo = 0, hi = tokenList.length - 1; lo <= hi; ) { const mid = (lo + hi) >> 1; - if (nodeTokens[mid].range[0] < rangeEnd) { + if (tokenList[mid].range[0] < rangeEnd) { lastTokenIndex = mid; lo = mid + 1; } else { @@ -1527,24 +1547,25 @@ export function getLastTokenBetween( // Fast path for the common case if (typeof filter !== "function") { - const token = nodeTokens[typeof skip === "number" ? lastTokenIndex - skip : lastTokenIndex]; - if (token === undefined || token.range[0] < rangeStart) return null; + const skipTo = lastTokenIndex - (skip ?? 0); + // Avoid indexing out of bounds + if (skipTo < 0) return null; + const token = tokenList[skipTo]; + if (token.range[0] < rangeStart) return null; return token; } if (typeof skip !== "number") { for (let i = lastTokenIndex; i >= 0; i--) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (token.range[0] < rangeStart) return null; if (filter(token)) return token; } } else { for (let i = lastTokenIndex; i >= 0; i--) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (token.range[0] < rangeStart) return null; if (filter(token)) { - // `<=` because user input may be negative - // TODO: gracefully handle the negative case in other methods if (skip <= 0) return token; skip--; } @@ -1592,13 +1613,13 @@ export function getLastTokensBetween( "includeComments" in countOptions && countOptions.includeComments; - let nodeTokens: Token[] | null = null; + let tokenList: Token[] | null = null; if (includeComments) { if (tokensWithComments === null) initTokensWithComments(); debugAssertIsNonNull(tokensWithComments); - nodeTokens = tokensWithComments; + tokenList = tokensWithComments; } else { - nodeTokens = tokens; + tokenList = tokens; } // This range is not invariant over node order. @@ -1606,24 +1627,24 @@ export function getLastTokensBetween( // Same as ESLint's implementation. const rangeStart = left.range[1], rangeEnd = right.range[0], - tokensLength = nodeTokens.length; + tokensLength = tokenList.length; - // Binary search for first token past the beginning of the `between` range + // Binary search for first token past "between" range start let sliceStart = tokensLength; for (let lo = 0; lo < sliceStart; ) { const mid = (lo + sliceStart) >> 1; - if (nodeTokens[mid].range[0] < rangeStart) { + if (tokenList[mid].range[0] < rangeStart) { lo = mid + 1; } else { sliceStart = mid; } } - // Binary search for first token past the end of the `between` range + // Binary search for first token past "between" range end let sliceEnd = tokensLength; for (let lo = sliceStart; lo < sliceEnd; ) { const mid = (lo + sliceEnd) >> 1; - if (nodeTokens[mid].range[0] < rangeEnd) { + if (tokenList[mid].range[0] < rangeEnd) { lo = mid + 1; } else { sliceEnd = mid; @@ -1632,20 +1653,20 @@ export function getLastTokensBetween( // Fast path for the common case if (typeof filter !== "function") { - if (typeof count !== "number") return nodeTokens.slice(sliceStart, sliceEnd); - return nodeTokens.slice(max(sliceStart, sliceEnd - count), sliceEnd); + if (typeof count !== "number") return tokenList.slice(sliceStart, sliceEnd); + return tokenList.slice(max(sliceStart, sliceEnd - count), sliceEnd); } const tokensBetween: Token[] = []; if (typeof count !== "number") { for (let i = sliceStart; i < sliceEnd; i++) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (filter(token)) tokensBetween.push(token); } } else { // Count is the number of preceding tokens, so we iterate in reverse for (let i = sliceEnd - 1; i >= sliceStart && tokensBetween.length < count; i--) { - const token = nodeTokens[i]; + const token = tokenList[i]; if (filter(token)) tokensBetween.unshift(token); } } @@ -1672,21 +1693,21 @@ export function getTokenByRangeStart( "includeComments" in rangeOptions && rangeOptions.includeComments; - let nodeTokens: Token[] | null = null; + let tokenList: Token[] | null = null; if (includeComments) { if (tokensWithComments === null) initTokensWithComments(); debugAssertIsNonNull(tokensWithComments); - nodeTokens = tokensWithComments; + tokenList = tokensWithComments; } else { - nodeTokens = tokens; + tokenList = tokens; } - // Binary search for the token that starts at the given index - for (let lo = 0, hi = nodeTokens.length; lo < hi; ) { + // Binary search for token starting at the given index + for (let lo = 0, hi = tokenList.length; lo < hi; ) { const mid = (lo + hi) >> 1; - const tokenStart = nodeTokens[mid].range[0]; + const tokenStart = tokenList[mid].range[0]; if (tokenStart === index) { - return nodeTokens[mid]; + return tokenList[mid]; } else if (tokenStart < index) { lo = mid + 1; } else {