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
99 changes: 97 additions & 2 deletions apps/oxlint/src-js/plugins/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,9 +397,104 @@ export function getLastToken(node: Node, skipOptions?: SkipOptions | number | Fi
* @param countOptions? - Options object. Same options as `getFirstTokens()`.
* @returns Array of `Token`s.
*/
// oxlint-disable-next-line no-unused-vars
export function getLastTokens(node: Node, countOptions?: CountOptions | number | FilterFn | null): Token[] {
throw new Error('`sourceCode.getLastTokens` not implemented yet'); // TODO
if (tokens === null) initTokens();
debugAssertIsNonNull(tokens);
debugAssertIsNonNull(comments);

// Maximum number of tokens to return
const count =
typeof countOptions === 'number'
? countOptions
: typeof countOptions === 'object' && countOptions !== null
? countOptions.count
: null;

// Function to filter tokens
const filter =
typeof countOptions === 'function'
? countOptions
: typeof countOptions === 'object' && countOptions !== null
? countOptions.filter
: null;

// Whether to return comment tokens
const includeComments =
typeof countOptions === 'object' &&
countOptions !== null &&
'includeComments' in countOptions &&
countOptions.includeComments;

// Source array of tokens to search in
let nodeTokens: Token[] | null = null;
if (includeComments) {
if (tokensWithComments === null) {
tokensWithComments = [...tokens, ...comments].sort((a, b) => a.range[0] - b.range[0]);
}
nodeTokens = tokensWithComments;
} else {
nodeTokens = tokens;
}

const { range } = node,
rangeStart = range[0],
rangeEnd = range[1];

// Binary search for first token within `node`'s range
const tokensLength = nodeTokens.length;
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 the first token outside `node`'s 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;
}
}

let lastTokens: Token[] = [];
if (typeof filter !== 'function') {
if (typeof count !== 'number') {
lastTokens = nodeTokens.slice(sliceStart, sliceEnd);
} else {
lastTokens = nodeTokens.slice(max(sliceStart, sliceEnd - count), sliceEnd);
}
} else {
if (typeof count !== 'number') {
lastTokens = [];
for (let i = sliceStart; i < sliceEnd; i++) {
const token = nodeTokens[i];
if (filter(token)) {
lastTokens.push(token);
}
}
} else {
lastTokens = [];
// Count is the number of tokens within range from the end so we iterate in reverse
for (let i = sliceEnd - 1; i >= sliceStart; i--) {
const token = nodeTokens[i];
if (filter(token)) {
lastTokens.unshift(token);
if (lastTokens.length === count) {
break;
}
}
}
}
}

return lastTokens;
}

/**
Expand Down
129 changes: 127 additions & 2 deletions apps/oxlint/test/tokens.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ import {
getTokensAfter,
getTokenAfter,
getFirstTokens,
getFirstToken,
getLastTokens,
getLastToken,
getFirstTokensBetween,
getFirstTokenBetween,
getLastTokenBetween,
getLastTokensBetween,
getTokenByRangeStart,
getTokensBetween,
getTokenOrCommentBefore,
getTokenOrCommentAfter,
} from '../src-js/plugins/tokens.js';
import { resetSourceAndAst } from '../src-js/plugins/source_code.js';
import type { Node } from '../src-js/plugins/types.js';
Expand Down Expand Up @@ -585,64 +596,178 @@ describe('when calling getFirstTokens', () => {
describe('when calling getFirstToken', () => {
/* oxlint-disable-next-line no-disabled-tests expect-expect */
it('is to be implemented');
/* oxlint-disable-next-line no-unused-expressions */
getFirstToken;
});

// https://github.com/eslint/eslint/blob/v9.39.1/tests/lib/languages/js/source-code/token-store.js#L851-L930
describe('when calling getLastTokens', () => {
/* oxlint-disable-next-line no-disabled-tests expect-expect */
it('is to be implemented');
it("should retrieve zero tokens from the end of a node's token stream", () => {
assert.deepStrictEqual(
getLastTokens(BinaryExpression, 0).map((token) => token.value),
[],
);
});

it("should retrieve one token from the end of a node's token stream", () => {
assert.deepStrictEqual(
getLastTokens(BinaryExpression, 1).map((token) => token.value),
['b'],
);
});

it("should retrieve more than one token from the end of a node's token stream", () => {
assert.deepStrictEqual(
getLastTokens(BinaryExpression, 2).map((token) => token.value),
['*', 'b'],
);
});

it("should retrieve all tokens from the end of a node's token stream", () => {
assert.deepStrictEqual(
getLastTokens(BinaryExpression, 9e9).map((token) => token.value),
['a', '*', 'b'],
);
});

it("should retrieve more than one token from the end of a node's token stream with count option", () => {
assert.deepStrictEqual(
getLastTokens(BinaryExpression, { count: 2 }).map((token) => token.value),
['*', 'b'],
);
});

it("should retrieve matched tokens from the end of a node's token stream with filter option", () => {
assert.deepStrictEqual(
getLastTokens(BinaryExpression, (t) => t.type === 'Identifier').map((token) => token.value),
['a', 'b'],
);
assert.deepStrictEqual(
getLastTokens(BinaryExpression, {
filter: (t) => t.type === 'Identifier',
}).map((token) => token.value),
['a', 'b'],
);
});

it("should retrieve matched tokens from the end of a node's token stream with filter and count options", () => {
assert.deepStrictEqual(
getLastTokens(BinaryExpression, {
count: 1,
filter: (t) => t.type === 'Identifier',
}).map((token) => token.value),
['b'],
);
});

it("should retrieve all tokens from the end of a node's token stream with includeComments option", () => {
assert.deepStrictEqual(
getLastTokens(BinaryExpression, {
includeComments: true,
}).map((token) => token.value),
['a', 'D', '*', 'b'],
);
});

it("should retrieve matched tokens from the end of a node's token stream with includeComments and count options", () => {
assert.deepStrictEqual(
getLastTokens(BinaryExpression, {
includeComments: true,
count: 3,
}).map((token) => token.value),
['D', '*', 'b'],
);
});

it("should retrieve matched tokens from the end of a node's token stream with includeComments and count and filter options", () => {
assert.deepStrictEqual(
getLastTokens(BinaryExpression, {
includeComments: true,
count: 3,
filter: (t) => t.type !== 'Punctuator',
}).map((token) => token.value),
['a', 'D', 'b'],
);
});
});

describe('when calling getLastToken', () => {
/* oxlint-disable-next-line no-disabled-tests expect-expect */
it('is to be implemented');
/* oxlint-disable-next-line no-unused-expressions */
getLastToken;
});

describe('when calling getFirstTokensBetween', () => {
/* oxlint-disable-next-line no-disabled-tests expect-expect */
it('is to be implemented');
/* oxlint-disable-next-line no-unused-expressions */
getFirstTokensBetween;
});

describe('when calling getFirstTokenBetween', () => {
/* oxlint-disable-next-line no-disabled-tests expect-expect */
it('is to be implemented');
/* oxlint-disable-next-line no-unused-expressions */
getFirstTokenBetween;
});

describe('when calling getLastTokensBetween', () => {
/* oxlint-disable-next-line no-disabled-tests expect-expect */
it('is to be implemented');
/* oxlint-disable-next-line no-unused-expressions */
getLastTokensBetween;
});

describe('when calling getLastTokenBetween', () => {
/* oxlint-disable-next-line no-disabled-tests expect-expect */
it('is to be implemented');
/* oxlint-disable-next-line no-unused-expressions */
getLastTokenBetween;
});

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;
});

describe('when calling getTokenByRangeStart', () => {
/* oxlint-disable-next-line no-disabled-tests expect-expect */
it('is to be implemented');
/* oxlint-disable-next-line no-unused-expressions */
getTokenByRangeStart;
});

describe('when calling getTokenOrCommentBefore', () => {
/* oxlint-disable-next-line no-disabled-tests expect-expect */
it('is to be implemented');
/* oxlint-disable-next-line no-unused-expressions */
getTokenOrCommentBefore;
});

describe('when calling getTokenOrCommentAfter', () => {
/* oxlint-disable-next-line no-disabled-tests expect-expect */
it('is to be implemented');
/* oxlint-disable-next-line no-unused-expressions */
getTokenOrCommentAfter;
});

describe('when calling getFirstToken & getTokenAfter', () => {
/* oxlint-disable-next-line no-disabled-tests expect-expect */
it('is to be implemented');
/* oxlint-disable-next-line no-unused-expressions */
getFirstToken;
/* oxlint-disable-next-line no-unused-expressions */
getTokenAfter;
});

describe('when calling getLastToken & getTokenBefore', () => {
/* oxlint-disable-next-line no-disabled-tests expect-expect */
it('is to be implemented');
/* oxlint-disable-next-line no-unused-expressions */
getLastToken;
/* oxlint-disable-next-line no-unused-expressions */
getTokenBefore;
});
Loading