Skip to content

Conversation

@lilnasy
Copy link
Contributor

@lilnasy lilnasy commented Nov 19, 2025

  • "Slow but working" implementation for Linter plugins: Token-related SourceCode APIs #14829 (comment)
  • Uses the parser from @typescript-eslint/typescript-estree for tokens. The source code is fully parsed and everything but the tokens is discarded.
  • Snapshots are generated in a typescript-eslint project and copied over with only formatting edits.
  • Direct commits from maintainers welcome.
  • Work in progress...

Tasks

Future work

  • getFirstToken
  • getFirstTokens
  • getLastToken
  • getLastTokens
  • getTokenBefore
  • getTokenOrCommentBefore
  • getTokensBefore
  • getTokenAfter
  • getTokenOrCommentAfter
  • getTokensAfter
  • getTokensBetween
  • getFirstTokenBetween
  • getFirstTokensBetween
  • getLastTokenBetween
  • getLastTokensBetween
  • getTokenByRangeStart
  • isSpaceBetween
  • isSpaceBetweenTokens

Decisions

  • @typescript-eslint/typescript-estree peer-depends on typescript. How should we package it? Currently, typescript is being made an optional dependency.
    • overlookmotel (on discord): ideally bundled, direct runtime dependency otherwise.
  • Deprecated methods are being removed altogether in ESLint 10: getTokenOrCommentBefore, getTokenOrCommentAfter, and isSpaceBetweenTokens. These are surface level deprecations: the functionality was merged with other methods (the includeComments: true option) and plugins can migrate with a one line change. I'm guessing we are targeting fairly modern, actively developed projects. Should we expose them?

@github-actions github-actions bot added A-linter Area - Linter A-cli Area - CLI A-linter-plugins Area - Linter JS plugins labels Nov 19, 2025
@lilnasy lilnasy changed the title Feat/linter/plugins/token methods feat(linter/plugins): Token-related SourceCode APIs (TS ESLint implementation) Nov 19, 2025
@github-actions github-actions bot added the C-enhancement Category - New feature or request label Nov 19, 2025
@lilnasy lilnasy force-pushed the feat/linter/plugins/token-methods branch 2 times, most recently from 3e3a5bb to dd03ef0 Compare November 19, 2025 12:35
@overlookmotel
Copy link
Member

overlookmotel commented Nov 19, 2025

Deprecated methods are being removed altogether in ESLint 10: getTokenOrCommentBefore, getTokenOrCommentAfter, and isSpaceBetweenTokens. These are surface level deprecations: the functionality was merged with other methods (the includeComments: true option) and plugins can migrate with a one line change.

Cameron and I had a bit of an argument about exactly this question!

We concluded in the end to keep all the deprecated methods, to maximize compatibility with older plugins, which may take some time to get updated (or in some cases, will never get updated). Our rationale is that most of these methods are just aliases, so no maintenance burden to keep them. The only one which is slightly different from its non-deprecated "brother" is isSpaceBetweenTokens, and that's pretty simple to implement - it just treats JSXText differently (if I remember right).

I'm guessing we are targeting fairly modern, actively developed projects.

That's true, but even actively developed projects may use old unmaintained plugins.

Note: I added the stubs in #15645. That PR also added tests which illustrate the difference in behavior between isSpaceBetween and isSpaceBetweenTokens.

Copy link
Member

@overlookmotel overlookmotel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly premature review, as it's still marked as draft, but I'm keen to get it merged so I thought I'd go through it now.

Apart from the comments below, this looks good to me. Sorry for the volume of comments - most are pretty small details.

Once we're happy with getTokens impl, I think we should merge this, and we can add more methods in separate PRs. No need to do the whole API in a single PR.

A couple of points which we can also leave to follow-ups:

  1. We should ideally lazy-load @typescript-eslint/typescript-estree package only when getTokens is first called.

  2. I assume TS-ESLint's parser also generates a ScopeManager. If it does, we may as well cache it, to avoid running scope analysis again if plugin does sourceCode.getTokens() followed by getting sourceCode.scopeManager.

@lilnasy
Copy link
Contributor Author

lilnasy commented Nov 20, 2025

  1. I assume TS-ESLint's parser also generates a ScopeManager. If it does, we may as well cache it, to avoid running scope analysis again if plugin does sourceCode.getTokens() followed by getting sourceCode.scopeManager.

@typescript-eslint/parser does. @typescript-eslint/typescript-estree does not.

The former delegates to the latter for parsing and lexing. I decided to use @typescript-eslint/typescript-estree directly to instantiate scope managers and parsers granularly.

@lilnasy lilnasy force-pushed the feat/linter/plugins/token-methods branch from faf3560 to de1f77e Compare November 20, 2025 12:21
graphite-app bot pushed a commit that referenced this pull request Nov 20, 2025
…pshot (#15906)

Contents of this test snapshot depends on the constructor names of scope class instances. This is a little fragile, as they can change depending on how the code is bundled (see #15861 (comment)).

At some point, we'll full minify the bundle, at which point class names will be come random gibberish like `e`, `t`, `aZ`, etc.

Avoid this problem by outputting `type` field of scope objects instead.
@overlookmotel
Copy link
Member

The former delegates to the latter for parsing and lexing. I decided to use @typescript-eslint/typescript-estree directly to instantiate scope managers and parsers granularly.

Ah ha that makes perfect sense. I had misunderstood.

@lilnasy lilnasy force-pushed the feat/linter/plugins/token-methods branch 6 times, most recently from e23e179 to 173e5a9 Compare November 20, 2025 20:05
@lilnasy lilnasy marked this pull request as ready for review November 20, 2025 21:03
@lilnasy lilnasy requested a review from camc314 as a code owner November 20, 2025 21:03
Copilot AI review requested due to automatic review settings November 20, 2025 21:03
@lilnasy lilnasy force-pushed the feat/linter/plugins/token-methods branch from 9fb3811 to ab159d9 Compare November 20, 2025 21:04
Copilot finished reviewing on behalf of lilnasy November 20, 2025 21:07
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements token-related SourceCode APIs for the oxlint JavaScript plugin system, specifically the getTokens method. The implementation uses @typescript-eslint/typescript-estree to parse source code and extract tokens, providing compatibility with ESLint's token API.

Key changes:

  • Implements sourceCode.getTokens() method with support for filters, comment inclusion, and before/after token counts
  • Defines comprehensive TypeScript token types (13 token types including Boolean, Keyword, Punctuator, JSXIdentifier, etc.)
  • Adds typescript and @typescript-eslint/typescript-estree as runtime dependencies

Reviewed Changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
pnpm-lock.yaml Adds lockfile entries for @typescript-eslint/typescript-estree and its dependencies
npm/oxlint/package.json Adds typescript as a production dependency for the published package
apps/oxlint/package.json Moves typescript to dependencies and adds typescript-estree as devDependency
apps/oxlint/src-js/plugins/tokens.ts Implements getTokens() with binary search and token type definitions
apps/oxlint/src-js/plugins/types.ts Removes duplicate Token interface, imports from tokens.ts instead
apps/oxlint/src-js/plugins/source_code.ts Integrates resetTokens() into cleanup logic
apps/oxlint/src-js/index.ts Exports all token-related types
apps/oxlint/test/tokens.test.ts Unit tests for getTokens() based on ESLint test cases
apps/oxlint/test/fixtures/sourceCode_token_methods/* E2E test fixture demonstrating token API usage
apps/oxlint/test/e2e.test.ts Adds E2E test for token methods
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Member

@overlookmotel overlookmotel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from the small comments below, looks good!

Comment on lines +66 to +67
// TODO: this test is failing because of a difference in program range between `espree` and `@typescript-eslint/typescript-estree`.
// Unskip after integrating `espree`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really weird one. ESLint is changing it's behavior in ESLint 10 to make the range of Program be [0, <length of source text>] (no matter whether there's leading/trailing comments/whitespace or not). TS-ESLint plans to follow suit.

https://eslint.org/blog/2025/10/whats-coming-in-eslint-10.0.0/#updates-to-program-ast-node-range-coverage
typescript-eslint/typescript-eslint#11026 (comment)

Oxc will make the change too, and everyone will be aligned (at last).

So I suggest modifying these tests and unskipping them by either:

  1. const Program = { range: [5, 36] } as Node; (to match current Espree). or
  2. Manually add the extra tokens to the array below, so the tests pass.

Either is fine to do, I think. Just need to make a comment explaining why.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PS Thanks for putting in the unglamorous work of upping the test coverage. Appreciate it.

Comment on lines +189 to +197
/**
* Number of following tokens to additionally return.
*/
afterCount = typeof afterCount === 'number' ? afterCount : 0;
Copy link
Member

@overlookmotel overlookmotel Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the JSDoc comment on this function (which is lifted from ESLint), params are either:

  1. (countOptions: CountOptions | FilterFn) or
  2. (beforeCount?: number, afterCount?: number)

That would mean that afterCount param should be ignored if countOptions is an object or function.

Does ESLint's implementation follow the JSDoc comment, or not actually do what it says it does? I guess we should do whatever ESLint does, but if behavior is contrary to the doc comment, it'd be good to add a comment explaining why.

Or we could open a bug report on ESLint.

Sorry, it really is insanely anal of me to question this!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, it really is insanely anal of me to question this!

YOU WORK FULL TIME ON A LINTER! It's part of the job description 😭.

Looking into it.

@lilnasy lilnasy force-pushed the feat/linter/plugins/token-methods branch from ab159d9 to e1202dd Compare November 21, 2025 04:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-cli Area - CLI A-linter Area - Linter A-linter-plugins Area - Linter JS plugins C-enhancement Category - New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants