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
82 changes: 79 additions & 3 deletions apps/oxlint/src-js/plugins/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,14 +527,90 @@ export function getTokensBefore(
* @param skipOptions? - Options object. Same options as `getFirstToken()`.
* @returns `Token`, or `null` if all were skipped.
*/
/* oxlint-disable no-unused-vars */
export function getTokenAfter(
nodeOrToken: NodeOrToken | Comment,
skipOptions?: SkipOptions | number | FilterFn | null,
): Token | null {
throw new Error('`sourceCode.getTokenAfter` not implemented yet'); // TODO
if (tokens === null) initTokens();
debugAssertIsNonNull(tokens);
debugAssertIsNonNull(comments);

let skip =
typeof skipOptions === 'number'
? skipOptions
: typeof skipOptions === 'object' && skipOptions !== null
? (skipOptions.skip ?? 0)
: 0;

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

const includeComments =
typeof skipOptions === 'object' &&
skipOptions !== null &&
'includeComments' in skipOptions &&
skipOptions.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 } = 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;
let startIndex = tokensLength;
for (let lo = 0; lo < startIndex; ) {
const mid = (lo + startIndex) >> 1;
if (nodeTokens[mid].range[0] < rangeEnd) {
lo = mid + 1;
} else {
startIndex = mid;
}
}

// Fast path for the common case
if (typeof filter !== 'function') {
if (typeof skip !== 'number') {
return nodeTokens[startIndex] ?? null;
} else {
return nodeTokens[startIndex + skip] ?? null;
}
} else {
if (typeof skip !== 'number') {
for (let i = startIndex; i < tokensLength; i++) {
const token = nodeTokens[i];
if (filter(token)) {
return token;
}
}
} else {
for (let i = startIndex; i < tokensLength; i++) {
const token = nodeTokens[i];
if (filter(token)) {
if (skip === 0) {
return token;
}
skip--;
}
}
}
}

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

/**
* Get the token that follows a given node or token.
Expand Down
102 changes: 98 additions & 4 deletions apps/oxlint/test/tokens.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,11 +303,105 @@ describe('when calling getTokenBefore', () => {
});
});

// https://github.com/eslint/eslint/blob/v9.39.1/tests/lib/languages/js/source-code/token-store.js#L461
describe('when calling getTokenAfter', () => {
/* oxlint-disable-next-line no-disabled-tests expect-expect */
it('is to be implemented');
/* oxlint-disable-next-line no-unused-expressions */
getTokenAfter;
it('should retrieve one token after a node', () => {
assert.strictEqual(getTokenAfter(VariableDeclaratorIdentifier)!.value, '=');
});

it('should skip a given number of tokens', () => {
assert.strictEqual(getTokenAfter(VariableDeclaratorIdentifier, 1)!.value, 'a');
assert.strictEqual(getTokenAfter(VariableDeclaratorIdentifier, 2)!.value, '*');
});

it('should skip a given number of tokens with skip option', () => {
assert.strictEqual(getTokenAfter(VariableDeclaratorIdentifier, { skip: 1 })!.value, 'a');
assert.strictEqual(getTokenAfter(VariableDeclaratorIdentifier, { skip: 2 })!.value, '*');
});

it('should retrieve matched token with filter option', () => {
assert.strictEqual(getTokenAfter(VariableDeclaratorIdentifier, (t) => t.type === 'Identifier')!.value, 'a');
assert.strictEqual(
getTokenAfter(VariableDeclaratorIdentifier, {
filter: (t) => t.type === 'Identifier',
})!.value,
'a',
);
});

it('should retrieve matched token with filter and skip options', () => {
assert.strictEqual(
getTokenAfter(VariableDeclaratorIdentifier, {
skip: 1,
filter: (t) => t.type === 'Identifier',
})!.value,
'b',
);
});

it('should retrieve one token or comment after a node with includeComments option', () => {
assert.strictEqual(
getTokenAfter(VariableDeclaratorIdentifier, {
includeComments: true,
})!.value,
'B',
);
});

it('should retrieve one token or comment after a node with includeComments and skip options', () => {
assert.strictEqual(
getTokenAfter(VariableDeclaratorIdentifier, {
includeComments: true,
skip: 2,
})!.value,
'C',
);
});

it('should retrieve one token or comment after a node with includeComments and skip and filter options', () => {
assert.strictEqual(
getTokenAfter(VariableDeclaratorIdentifier, {
includeComments: true,
skip: 2,
filter: (t) => t.type.startsWith('Block'),
})!.value,
'D',
);
});

it('should retrieve the next node if the comment at the first of source code is specified.', () => {
resetSourceAndAst();
sourceText = '/*comment*/ a + b';
// TODO: replace this verbatim range with `ast.comments[0]`
const token = getTokenAfter({ range: [0, 12] } as Node)!;

assert.strictEqual(token.value, 'a');
resetSourceAndAst();
});

it('should retrieve the next comment if the last token is specified.', () => {
resetSourceAndAst();
sourceText = 'a + b /*comment*/';
// TODO: replace this verbatim range with `ast.tokens[2]`
const token = getTokenAfter({ range: [4, 5] } as Node, {
includeComments: true,
});

assert.strictEqual(token!.value, 'comment');
resetSourceAndAst();
});

it('should retrieve null if the last comment is specified.', () => {
resetSourceAndAst();
sourceText = 'a + b /*comment*/';
// TODO: replace this verbatim range with `ast.comments[0]`
const token = getTokenAfter({ range: [6, 17] } as Node, {
includeComments: true,
});

assert.strictEqual(token, null);
resetSourceAndAst();
});
});

// https://github.com/eslint/eslint/blob/v9.39.1/tests/lib/languages/js/source-code/token-store.js#L363-L459
Expand Down
Loading