Skip to content

Commit 4c240ac

Browse files
lilnasyoverlookmotel
authored andcommitted
feat(linter/plugins): implement SourceCode#getTokenByRangeStart()
1 parent 7b8d578 commit 4c240ac

File tree

2 files changed

+66
-7
lines changed

2 files changed

+66
-7
lines changed

apps/oxlint/src-js/plugins/tokens.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export interface CountOptions {
3636
}
3737

3838
/**
39-
* Options for various `SourceCode` methods e.g. `getTokenByRangeStart`.
39+
* Options for `getTokenByRangeStart`.
4040
*/
4141
export interface RangeOptions {
4242
/** `true` to include comment tokens in the result */
@@ -1639,9 +1639,40 @@ export function getLastTokensBetween(
16391639
* @param rangeOptions - Options object.
16401640
* @returns The token starting at index, or `null` if no such token.
16411641
*/
1642-
// oxlint-disable-next-line no-unused-vars
16431642
export function getTokenByRangeStart(index: number, rangeOptions?: RangeOptions | null): Token | null {
1644-
throw new Error('`sourceCode.getTokenByRangeStart` not implemented yet'); // TODO
1643+
if (tokens === null) initTokens();
1644+
debugAssertIsNonNull(tokens);
1645+
debugAssertIsNonNull(comments);
1646+
1647+
const includeComments =
1648+
typeof rangeOptions === 'object' &&
1649+
rangeOptions !== null &&
1650+
'includeComments' in rangeOptions &&
1651+
rangeOptions.includeComments;
1652+
1653+
let nodeTokens: Token[] | null = null;
1654+
if (includeComments) {
1655+
if (tokensWithComments === null) initTokensWithComments();
1656+
debugAssertIsNonNull(tokensWithComments);
1657+
nodeTokens = tokensWithComments;
1658+
} else {
1659+
nodeTokens = tokens;
1660+
}
1661+
1662+
// Binary search for the token that starts at the given index
1663+
for (let lo = 0, hi = nodeTokens.length; lo < hi; ) {
1664+
const mid = (lo + hi) >> 1;
1665+
const tokenStart = nodeTokens[mid].range[0];
1666+
if (tokenStart === index) {
1667+
return nodeTokens[mid];
1668+
} else if (tokenStart < index) {
1669+
lo = mid + 1;
1670+
} else {
1671+
hi = mid;
1672+
}
1673+
}
1674+
1675+
return null;
16451676
}
16461677

16471678
// Regex that tests for whitespace.

apps/oxlint/test/tokens.test.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,11 +1136,39 @@ describe('when calling getTokensBetween', () => {
11361136
});
11371137
});
11381138

1139+
// https://github.com/eslint/eslint/blob/v9.39.1/tests/lib/languages/js/source-code/token-store.js#L1526-L1562
11391140
describe('when calling getTokenByRangeStart', () => {
1140-
/* oxlint-disable-next-line no-disabled-tests expect-expect */
1141-
it('is to be implemented');
1142-
/* oxlint-disable-next-line no-unused-expressions */
1143-
getTokenByRangeStart;
1141+
it('should return identifier token', () => {
1142+
const { type, value } = getTokenByRangeStart(9)!;
1143+
1144+
expect(type).toEqual('Identifier');
1145+
expect(value).toEqual('answer');
1146+
});
1147+
1148+
it("should return null when token doesn't exist", () => {
1149+
expect(getTokenByRangeStart(10)).toBeNull();
1150+
});
1151+
1152+
it('should return a comment token when includeComments is true', () => {
1153+
const { type, value } = getTokenByRangeStart(15, {
1154+
includeComments: true,
1155+
})!;
1156+
1157+
expect(type).toEqual('Block');
1158+
expect(value).toEqual('B');
1159+
});
1160+
1161+
it('should not return a comment token at the supplied index when includeComments is false', () => {
1162+
expect(
1163+
getTokenByRangeStart(15, {
1164+
includeComments: false,
1165+
}),
1166+
).toBeNull();
1167+
});
1168+
1169+
it('should not return comment tokens by default', () => {
1170+
expect(getTokenByRangeStart(15)).toBeNull();
1171+
});
11441172
});
11451173

11461174
describe('when calling getTokenOrCommentBefore', () => {

0 commit comments

Comments
 (0)