Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 99 additions & 11 deletions apps/oxlint/src-js/plugins/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1105,27 +1105,115 @@ export function getTokensAfter(

/**
* Get all of the tokens between two non-overlapping nodes.
* @param nodeOrToken1 - Node before the desired token range.
* @param nodeOrToken2 - Node after the desired token range.
* @param left - Node or token before the desired token range.
* @param right - Node or token after the desired token range.
* @param countOptions? - Options object. If this is a function then it's `options.filter`.
* @returns Array of `Token`s between `nodeOrToken1` and `nodeOrToken2`.
* @returns Array of `Token`s between `left` and `right`.
*/
/**
* Get all of the tokens between two non-overlapping nodes.
* @param nodeOrToken1 - Node before the desired token range.
* @param nodeOrToken2 - Node after the desired token range.
* @param left - Node or token before the desired token range.
* @param right - Node or token after the desired token range.
* @param padding - Number of extra tokens on either side of center.
* @returns Array of `Token`s between `nodeOrToken1` and `nodeOrToken2`.
* @returns Array of `Token`s between `left` and `right`.
*/
/* oxlint-disable no-unused-vars */
export function getTokensBetween(
nodeOrToken1: NodeOrToken | Comment,
nodeOrToken2: NodeOrToken | Comment,
left: NodeOrToken | Comment,
right: NodeOrToken | Comment,
countOptions?: CountOptions | number | FilterFn | null,
): Token[] {
throw new Error('`sourceCode.getTokensBetween` not implemented yet'); // TODO
if (tokens === null) initTokens();
debugAssertIsNonNull(tokens);
debugAssertIsNonNull(comments);

const count = typeof countOptions === 'object' && countOptions !== null ? countOptions.count : null;

const padding = typeof countOptions === 'number' ? countOptions : 0;

const filter =
typeof countOptions === 'function'
? countOptions
: typeof countOptions === 'object' && countOptions !== null
? countOptions.filter
: null;

const includeComments =
typeof countOptions === 'object' &&
countOptions !== null &&
'includeComments' in countOptions &&
countOptions.includeComments;

let nodeTokens: Token[];
if (includeComments) {
if (tokensWithComments === null) initTokensWithComments();
debugAssertIsNonNull(tokensWithComments);
nodeTokens = tokensWithComments;
} else {
nodeTokens = tokens;
}

// This range is not invariant over node order.
// The first argument must be the left node.
// Same as ESLint's implementation.
const rangeStart = left.range[1],
rangeEnd = right.range[0],
tokensLength = nodeTokens.length;

// Binary search for first token past the beginning of the `between` range
let sliceStart = tokensLength;
for (let lo = 0; lo < sliceStart; ) {
const mid = (lo + sliceStart) >> 1;
if (nodeTokens[mid].range[0] < rangeStart) {
lo = mid + 1;
} else {
sliceStart = mid;
}
}

// Binary search for first token past the end of the `between` range
let sliceEnd = tokensLength;
for (let lo = sliceStart; lo < sliceEnd; ) {
const mid = (lo + sliceEnd) >> 1;
if (nodeTokens[mid].range[0] < rangeEnd) {
lo = mid + 1;
} else {
sliceEnd = mid;
}
}

// Apply padding
sliceStart = max(0, sliceStart - padding);
sliceEnd += padding;

let tokensBetween: Token[];
if (typeof filter !== 'function') {
if (typeof count !== 'number') {
tokensBetween = nodeTokens.slice(sliceStart, sliceEnd);
} else {
tokensBetween = nodeTokens.slice(sliceStart, min(sliceStart + count, sliceEnd));
}
} else {
if (typeof count !== 'number') {
tokensBetween = [];
for (let i = sliceStart; i < sliceEnd; i++) {
const token = nodeTokens[i];
if (filter(token)) {
tokensBetween.push(token);
}
}
} else {
tokensBetween = [];
for (let i = sliceStart; i < sliceEnd && tokensBetween.length < count; i++) {
const token = nodeTokens[i];
if (filter(token)) {
tokensBetween.push(token);
}
}
}
}

return tokensBetween;
}
/* oxlint-enable no-unused-vars */

/**
* Get the first token between two non-overlapping nodes.
Expand Down
26 changes: 22 additions & 4 deletions apps/oxlint/test/tokens.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1111,11 +1111,29 @@ describe('when calling getLastTokenBetween', () => {
getLastTokenBetween;
});

// https://github.com/eslint/eslint/blob/v9.39.1/tests/lib/languages/js/source-code/token-store.js#L1489-L1524
describe('when calling getTokensBetween', () => {
/* oxlint-disable-next-line no-disabled-tests expect-expect */
it('is to be implemented');
/* oxlint-disable-next-line no-unused-expressions */
getTokensBetween;
it('should retrieve zero tokens between adjacent nodes', () => {
expect(getTokensBetween(BinaryExpression, CallExpression).map((token) => token.value)).toEqual([]);
});

it('should retrieve one token between nodes', () => {
expect(getTokensBetween(BinaryExpression.left, BinaryExpression.right).map((token) => token.value)).toEqual(['*']);
});

it('should retrieve multiple tokens between non-adjacent nodes', () => {
expect(getTokensBetween(VariableDeclaratorIdentifier, BinaryExpression.right).map((token) => token.value)).toEqual([
'=',
'a',
'*',
]);
});

it('should retrieve surrounding tokens when asked for padding', () => {
expect(
getTokensBetween(VariableDeclaratorIdentifier, BinaryExpression.left, 2).map((token) => token.value),
).toEqual(['var', 'answer', '=', 'a', '*']);
});
});

describe('when calling getTokenByRangeStart', () => {
Expand Down
Loading