Commit Graph

86 Commits

Author SHA1 Message Date
Nicolas Le Cam 1884833279 feat(linter/plugins): implement SourceCode.getDisableDirectives method (#21029)
Used by [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/0e023e3ad4dfb22ace33ef70c420513946528fa4/rules/utils/eslint-directive.js)

It crashes when trying to use it through jsPlugins (for unicorn/expiring-todo-comments)

ESLint [documentation](https://eslint.org/docs/latest/extend/languages#the-sourcecode-object) says its optional but unicorn doesn't seem to guard against its absence

---------

Signed-off-by: Nicolas Le Cam <niko.lecam@gmail.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
2026-05-04 21:17:38 +01:00
overlookmotel c0278abf55 perf(napi/parser, linter/plugins): use Int32Array in raw transfer deserializer (#21132)
Use `int32` (`Int32Array`) instead of `uint32`(`Uint32Array`) for getting data from buffer in raw transfer deserializer. The buffer is 2 GiB in size, so all offsets within the buffer can be represented as a `u31` which can be stored in an `i32` without any loss, and with no risk of being interpreted as negative numbers.

Same as in #21129, the advantage of fetching offsets from an `Int32Array` is that V8 statically knows the value can be stored in an SMI, its native integer type, without any range checks.
2026-04-07 17:25:41 +00:00
overlookmotel 43482c7bb1 perf(linter/plugins): use >> not >>> in binary search loops (#21129)
`>>` is cheaper than `>>>` because `>>` produces a 32-bit _signed_ integer which is V8's native number type (SMI). `>>>` produces a 32-bit _unsigned_ integer, which needs to be boxed and stored on the heap.

Source text in raw transfer is limited to 1 GiB, and therefore source offsets, number of lines, number of tokens, and number of comments after all less than `1 << 30`. Therefore even the sum of 2 of them cannot reach `1 << 31` (the maximum positive integer which can be stored as a positive signed 31-bit int. Therefore it's safe to use `>>` in these binary loops.
2026-04-07 17:10:32 +00:00
overlookmotel a4ac3ce514 refactor(linter/plugins): import getNodeLoc instead of injecting it (#21026)
Refacfor. In a strange pattern, we were injecting `getNodeLoc` into `deserialize.js` module on every call to `deserializeProgramOnly`. It's a static function, so just import it.
2026-04-03 22:05:22 +00:00
overlookmotel 3b7dec4dbb perf(napi/parser, linter/plugins): use utf8Slice for decoding UTF-8 strings (#21022)
Optimize raw transfer string decoding.

Replace `TextDecoder("utf8")` with `Buffer.prototype.utf8Slice` to decode Rust UTF-8 strings to JS strings.

Benchmarks show this is an average 5% speed-up in `deserializeStr`, though the reason why is unclear. It skips various checks, and avoids creating a temporary `Uint8Array` (which is required with `TextDecoder.decode`) which trims 60 bytes of the size of the assembly created for `deserializeStr`. But that's not enough to explain the speed-up 🤷. Anyway, take the win.
2026-04-03 22:05:22 +00:00
overlookmotel 5984a6673b perf(linter/plugins): recycle Location objects (#20491)
Add an object pool for `Location` objects, and recycle them across files.

Usually, only a minority of AST nodes, tokens, and comments will have their `loc` property accessed, so the pool is not pre-populated - `Location` objects are only created on demand.

Like the other object pools, once warmed up over the first batch of files, the fast path for "use an existing object from the pool" will be consistently taken and no further objects will be allocated. All `Location` objects will soon graduate to "old space" where they don't impede or trigger minor GC.
2026-03-21 12:46:33 +00:00
overlookmotel 9a622c79b4 perf(linter/plugins): lazy deserialize tokens and comments (#20474)
Performance improvement to tokens and comments APIs.

## The problem

Previously, all tokens and comments methods would deserialize *all* tokens/comments into an array of `Token` / `Comment` / `Token | Comment` objects, and then binary search through those arrays to find the token(s) / comment(s) they're looking for.

This has 2 major disadvantages:

1. Files typically contain *a lot* of tokens (even more than the number of AST nodes). Deserializing them all is very costly (up to 30% of total Oxlint runtime when run with only a JS rule which just calls a tokens-related method).

2. The binary searches these methods do are quite expensive. Even in TurboFan-optimized code, accessing `token.start` involves getting pointer to the `Token` object from the `tokens` array, an "is this object a `Token`?" safety check, then reading the `start` field from the `Token` - all just to access a single `u32`, and that happens over and over.

## This PR's solution

Solve both these problems by making tokens and comments methods read `start` / `end` offsets directly from the buffers which contain the tokens/comments data.

This data is tightly packed in memory, and strongly typed (read from `Uint32Array`s), so getting `start` / `end` of a token requires no indirection and no type checks.

More importantly, it removes the need to deserialize all tokens / comments upfront. The desired token(s) are located, touching only the buffer, and then *only* the ones which need to be returned to rule code are deserialized into JS objects.

If a rule accesses `ast.tokens`, `ast.comments`, or `sourceCode.tokensAndComments` then all tokens / comments need to be deserialized, as they're all returned to the rule as an array - but that's unavoidable. This PR doesn't make that any cheaper, but it doesn't make it measurably more costly either.

But where no rule requires the full array of tokens / comments, and they only use token/comment search methods (e.g. `getFirstToken`, `getCommentsBefore`), a great deal of work will be saved. This covers the vast majority of rules.

## Implementation details

The main complication is the `includeComments` option to tokens methods. When `true`, search needs to be over a combined set of both tokens and comments.

When `includeComments: true` option is passed to a tokens method, a buffer is created containing data about all tokens and comments, interleaved in source code order. This buffer can then be used for binary search in tokens methods.

Whether each token / comment has been deserialized already or not is tracked by a "deserialized" flag in the tokens/comments buffers. Each token / comment in the buffer is 16 bytes. This flag lives in byte 15. For tokens, this byte is always already 0 in the buffer when it arrives from Rust side. For comments, we manually set `comment.content = CommentContent::None;` for every comment on Rust side. `comment.content` is positioned at byte 15 in the `Comment` struct, and `CommentContent::None` is stored as 0.

## Possible future improvements

### SoA storage

Binary search operates only on `start` field of tokens / comments, which are 16 bytes apart in the buffer. It would be more efficient if tokens were stored in struct-of-arrays (SoA) style so all `start` values were tightly packed together. This would reduce CPU cache misses in the hot loops of binary searches.

### Pre-compute tokens-and-comments buffer on Rust side

The buffer containing tokens and comments, required to support `includeComments: true`, is currently generated on JS side (but lazily). We could move that to Rust side, which would be faster. However, it might be redundant work in many cases because the buffer is only required if a rule uses `includeComments: true`.

We could alternatively keep the laziness optimization, by calling back into Rust to build the buffer on demand - but JS-Rust calls have a cost too. Maybe communicating via `Atomics` would be faster than an actual function call?

If we had a way to share buffers with WASM, optimal solution might be to generate the buffer lazily (as now) but in WASM, which would be faster for this kind of pure number-crunching, but without the overhead of calling into Rust.
2026-03-21 12:24:11 +00:00
overlookmotel ef41ffadd6 refactor(linter/plugins): move comments methods into own file (#20363)
Pure refactor. Move `sourceCode` methods related to comments into a separate file `comments_methods.ts`. Does not alter any of the code, just moves it.
2026-03-14 12:19:28 +00:00
overlookmotel 9cd612f1e9 perf(linter/plugins): recycle comment objects (#20362)
Apply the same optimization as #19978 to comments - hold a pool of `Comment` objects, and re-use those objects rather than creating new objects each time.

Same as with `Token`s, `loc` property is a getter which calculates `loc` lazily, and caches it in a private property.
2026-03-14 12:19:27 +00:00
overlookmotel 5e4df8008c refactor(linter/plugins): split tokens initialization from tokens methods (#19981)
Pure refactor. `tokens.ts` was getting too large. Split it into 2 files:

* `tokens.ts`: Tokens lifecycle methods - initialization, deserialization, and reset.
* `tokens_methods.ts`: Public methods on `SourceCode` e.g. `sourceCode.getFirstToken`.
2026-03-03 23:23:50 +00:00
overlookmotel 05ccf9f21e perf(linter/plugins): transfer tokens via raw transfer (#19893)
Transfer tokens via raw transfer in Oxlint JS plugins.

Deserializer is written by hand. `Token` is not a normal struct, so deserializer cannot be generated.
2026-03-01 21:47:15 +00:00
overlookmotel 5ca0d9a385 refactor(linter/plugins): combine functions for getting tokens (#19529)
Refactor. Logic related to tokens was spread across 2 files. Move it all into `tokens.ts`.

Also, fix one small bug where there'd be an error when linting an empty file.
2026-02-24 11:31:05 +00:00
camc314 6a6513c6ab perf(linter/plugins): use Oxc tokens in plugins (#19498)
Builds on #19497. Use the tokens generated by Oxc's parser in linter plugins, instead of running TS parser to tokenize source.

This is a sizeable perf gain, and also allows us to remove TS parser from `oxlint`'s bundle (#19531).

Additionally, our parser is more accurate  than TypeScript - we handle HTML comments and space before slash in closing JSX elements (`< /div>`) - so this fixes a few conformance tests.
2026-02-24 11:31:04 +00:00
overlookmotel abf3a7b0d6 refactor(linter/plugins): simplify implementation of sourceCode.parserServices (#18645)
Pure refactor. No point passing an object around through functions, when `parserServices` is always an empty object, as Oxlint provides no parser services. Just define it as an empty object inline.
2026-01-28 02:26:55 +00:00
overlookmotel 1363943f3d test(linter/plugins): remove directives when parsing as ES3 (#18636)
In ES3, directives didn't exist yet, and therefore `"use strict"` is treated as a standard string literal, rather than a directive.

In conformance tests, when test case specifies ES3, walk the AST and set `directive` property of all `ExpressionStatement`s to `null`. This transforms any directives in AST into plain string literals.

Combined with #18632 which fixed `"use strict"` processing in TS-ESLint, this means there are 8 ESLint test cases we no longer have to skip. More importantly, we're less likely to run into erroneous test failures when testing other plugins, which are laborious to diagnose.
2026-01-27 18:42:21 +00:00
overlookmotel 332d2efb56 fix(linter/plugins): add jsx property to parserOptions.ecmaFeatures (#18549)
Add `jsx` getter to `context.languageOptions.parserOptions.ecmaFeatures`. `eslint-plugin-stylistic` requires it, probably other plugins do too.
2026-01-25 21:31:27 +00:00
overlookmotel 6ac09e2a9d feat(linter/plugins): support source text not being at start of buffer (#18375)
Previous raw transfer required that the source text start exactly at the start of the buffer. In linter, relax this restriction and handle when source text is stored elsewhere in the buffer.

This is necessary for stripping BOM from source (#18376), as then the source text used on JS side starts 3 bytes after the start of the buffer.
2026-01-22 02:16:53 +00:00
Peter Wagenet 7a0ca9999b fix(linter/plugins): use correct ScriptKind for tokens (#17185)
When parsing tokens for JS plugins, the TypeScript parser was always using `ScriptKind.TSX`, regardless of file extension. This caused TypeScript to incorrectly parse generic arrow functions like `<T>() => {}` in `.ts` files, creating bogus `JsxText` tokens that overlap with comments.

This PR adds an `is_jsx` flag to `RawTransferMetadata`. Rust side sets the flag depending on source type, and JS side uses that information to pass the correct `ScriptKind` for the file to TypeScript parser.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: overlookmotel <theoverlookmotel@gmail.com>
2026-01-15 19:06:08 +00:00
overlookmotel b2b87c608f feat(linter/plugins): add getRange and getLoc methods to SourceCode (#17179)
ESLint has undocumented `getRange` and `getLoc` methods on `SourceCode`. Add them.

These are the last 2 missing methods on `SourceCode` (according to Claude).

Also add more tests for correct functioning of `range` and `loc` properties.
2025-12-20 15:49:56 +00:00
overlookmotel 563b039da5 feat(linter/plugins): add SourceCode#getLines method (#17177)
ESLint has an undocumented `SourceCode#getLines` method. Implement it.

Discovered the existence of this method from conformance tests.
2025-12-20 13:52:49 +00:00
overlookmotel da509be78a refactor(linter/plugins): convert global properties to vars with TSDown plugin, instead of manually (#16961)
Previously we had a pattern of destructuring properties of global objects into top-level vars. e.g.:

```js
const { keys: ObjectKeys, hasOwn } = Object,
  { isArray } = Array;
```

Doing this is more performant, but it makes the code harder to read.

Instead, write code normally, and use a TSDown plugin to convert:

```js
const keys = Object.keys(obj);
```

to:

```js
import { ObjectKeys } from "../utils/globals.ts";
const keys = ObjectKeys(obj);
```

Defining all the destructured globals in a single file (`utils/globals.ts`) is advantageous for bundle size. If the TSDown plugin created top level `const ObjectKeys = Object.keys;` statements in each file, then you can end up with this in bundle as minifier doesn't de-duplicate:

```js
const ObjectKeys = Object.keys;
const ObjectKeys$1 = Object.keys;
const ObjectKeys$2 = Object.keys;
```

The globals we use have to be manually added to `utils/globals.ts`, but TSDown plugin warns when this needs to be done, and doesn't generate a broken build in the meantime.

This plugin is run in debug builds too, so that the tests will catch any bugs.
2025-12-16 17:33:37 +00:00
camc314 291b57bfb2 feat(ast_tools): generate TS declaration files for deserializer and walk files (#16912)
this fixes some ts-expect-errors in oxlint by generating declaration files.

We could also consider using oxc_isolated_declarations to generate these
2025-12-15 20:50:16 +00:00
overlookmotel f814c82292 fix(linter/plugins)!: use TypeScript parser directly for getting tokens (#16805)
Previously we used `@typescript-eslint/typescript-estree` parser to tokenize a source text. `@typescript-eslint/typescript-estree` uses TypeScript parser internally, and then builds the tokens array by traversing TypeScript's AST.

Instead, use TypeScript parser directly, and copy the code from TS-ESLint for converting tokens from TypeScript's format to TS-ESTree format.

### Why?

This has a few advantages:

1. Remove a dependency.
2. TS-ESLint's tokens did not have `start` or `end` properties, which we want for consistency with our AST. By reimplementing ourselves, we can add those properties to tokens.
3. TS-ESLint does a bunch of other work besides compiling the tokens array - it converts the AST too. We discard the AST that TS-ESLint produces, so this work is pointless. By cutting out TS-ESLint, we also cut out that unnecessary work, and do only what we need to - tokenizing.
4. TS-ESLint would throw an error if TypeScript produces any errors (even non-fatal ones), and there is no option to tell TS-ESLint not to do that. This caused a bunch of conformance tests to fail, because ESLint's tests include invalid code.
5. Can add workarounds for TypeScript bugs - e.g. not being able to parse JSX closing fragments containing line breaks (`<>hello<\n/>`).

### Implementation detail

We no longer need to get comments from TypeScript. We use the comments produced by Oxc parser instead.

### `CommentToken` type

A side effect is that there's no longer any need for separate `Comment` and `CommentToken` types - as we use our own comments now. So this PR removes `CommentToken` type.

Have marked this as a breaking change due to removal of this type.
2025-12-13 22:55:01 +00:00
overlookmotel fabe6f7509 refactor(linter/plugins): add TODO comments for @ts-expect-errors which need fixing (#16796)
Only alters comments.

* Make sure all `// @ts-expect-error` comments include "TODO" where it's an issue we need to fix.
* Format all the "TODO" comments consistently.
2025-12-13 14:33:39 +00:00
overlookmotel b38c2d06be feat(linter/plugins): add SourceCode#lineStartIndices getter (#16510)
Add a getter `lineStartIndices` to `SourceCode`. ESLint has this property, but it's undocumented. Possible that some ESLint plugins rely on it (if it's there, people will use it!).
2025-12-04 19:14:21 +00:00
overlookmotel ba93ffc131 feat(linter/plugins): add SourceCode#tokensAndComments getter (#16503)
Add a getter `tokensAndComments` to `SourceCode`. ESLint has this property, but it's undocumented, but many of ESLint's built-in rules rely on it. Probably other ESLint plugins rely on it too.
2025-12-04 16:39:56 +00:00
overlookmotel d2ca540080 feat(linter/plugins): add SourceCode#isESTree property (#16499)
Add `isESTree` property to `SourceCode`. ESLint's has this property, though it's undocumented.
2025-12-04 16:39:55 +00:00
overlookmotel a9b9298e98 docs(linter/plugins): add JSDoc comments to SourceCode properties (#16497)
Docs-only change. Add JSDoc comments documenting all the getters on `SourceCode`.
2025-12-04 16:39:55 +00:00
overlookmotel 3f3d646280 refactor(linter/plugins): imports use .ts extension (#16390)
Pure refactor. Use `.ts` extension when importing `.ts` files.
2025-12-02 14:08:49 +00:00
overlookmotel 9149a26f4f fix(linter/plugins, napi/parser): deep freeze visitor keys (#16293)
Visitor keys object used by `oxlint` and `napi/parser` is frozen, but the individual arrays of keys were not. Deep freeze the whole structure to prevent user modifying it.
2025-11-30 14:04:52 +00:00
overlookmotel 71502090c8 feat(linter/plugins): implement SourceCode#getNodeByRangeIndex (#16256)
Implement `SourceCode#getNodeByRangeIndex` method.

Behaves the same as ESLint's version, but this implementation should be more performant as it's much simpler, avoiding ESLint's `Traverser` abstraction, and exiting loops earlier.
2025-11-29 02:42:34 +00:00
Boshen 523a30a016 style(all): apply oxfmt default options (#16091)
Embrace the defaults!

---------

Co-authored-by: Yuji Sugiura <y.sugiura.0316@gmail.com>
2025-11-25 15:56:24 +09:00
overlookmotel ae137042f5 refactor(linter/plugins): rename assertion functions (#15968)
Rename assertion functions to clarify that they're not "real" assertions which we can rely on for validating input, and are only for compile-time testing or debug assertions.

`typeAssertIs` is not a great name, but I couldn't come up with anything better. `debugAssertIs` would be not be accurate, as it'd imply that in debug builds in performs an actual assertion, which it doesn't.

In any case, we should replace `typeAssertIs` with specialized assertion functions which do actually perform runtime assertions in debug builds (e.g. `typeAssertIs<Fix>()` -> `debugAssertIsFix()`).
2025-11-22 15:54:17 +00:00
Arsh 986cac1899 feat(linter/plugins): Token-related SourceCode APIs (TS ESLint implementation) (#15861)
- "Slow but working" implementation for
https://github.com/oxc-project/oxc/issues/14829#issuecomment-3512679496
- Uses the parser from `@typescript-eslint/typescript-estree` for
tokens. The source code is fully parsed and everything but the tokens
and comments is discarded.
- Tests are directly adapted from `eslint`.
- Direct commits from maintainers welcome.

### Tasks
- [x] `getTokens`
- [x] Move token types to `src-js/plugins/tokens.ts`
- [x] Bundle typescript, handling `_filename` usage in ES modules.
- [x] ~[Prevent class name mangling by
`tsdown`](https://github.com/oxc-project/oxc/pull/15861#discussion_r2542816947).
Could be deferred.~
https://github.com/oxc-project/oxc/pull/15906#pullrequestreview-3487271352
- [x] [Test regex-divide ambiguous
syntax](https://github.com/oxc-project/oxc/pull/15861#discussion_r2542919409).
- [x] Test for conformance directly against `eslint` tests.
- [ ] [Parse as JSX only if source text is
JSX](https://github.com/oxc-project/oxc/pull/15861#discussion_r2542850640).
Could be deferred.
- [ ] Lazy load the parser on demand. Could be deferred.

### Future work
- [ ] `getFirstToken`
- [ ] `getFirstTokens`
- [ ] `getLastToken`
- [ ] `getLastTokens`
- [ ] `getTokenBefore`
- [ ] `getTokenOrCommentBefore`
- [ ] `getTokensBefore`
- [ ] `getTokenAfter`
- [ ] `getTokenOrCommentAfter`
- [ ] `getTokensAfter`
- [ ] `getTokensBetween`
- [ ] `getFirstTokenBetween`
- [ ] `getFirstTokensBetween`
- [ ] `getLastTokenBetween`
- [ ] `getLastTokensBetween`
- [ ] `getTokenByRangeStart`
- [ ] `isSpaceBetween`
- [ ] `isSpaceBetweenTokens`

### Decisions
- [x] `@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.**
- [x] 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?
- **[overlookmotel and
camc314](https://github.com/oxc-project/oxc/pull/15861#issuecomment-3552651295):
yes.**
- [x] `Program` range is different between ESLint and TS-ESLint. Which
one should we follow?
-
**[overlookmotel](https://github.com/oxc-project/oxc/pull/15861#discussion_r2547915407):
they are going to both change and align soon, we should update our range
calculation to match when they do.**

---------

Co-authored-by: overlookmotel <theoverlookmotel@gmail.com>
2025-11-21 17:39:02 +00:00
overlookmotel 512e43a204 refactor(linter/plugins): move assertions into own file (#15945)
Pure refactor. Split up `plugins/utils.ts` into 3 files in `utils` directory.

Most importantly, assertion functions are by themselves in an `asserts.ts` file. This paves the way for the next PR (#15946).
2025-11-21 14:42:25 +00:00
overlookmotel a72b093b05 refactor(linter/plugins): enable TS strictNullChecks (#15886)
Adapted from #15868. Enable `strictNullChecks` in TSConfig for `apps/oxlint`.

Rather than using `!` all over the place, I've added a function `assertIsNonNull`, which performs the same function. Idea is that we can later on produce a debug build where `assertIsNonNull` does an actual runtime assertion (rather than just a TS `asserts`) that the value is not `null` or `undefined`. We can use the debug build in tests, and catch any logic errors.

Unfortunately, TypeScript doesn't seem able to track side effects, so there seems to be no way to tell it "after calling `initAst()`, `ast` is always non-null". Don't want to switch to a having a `getAst` function which returns a non-null value, as that'd introduce unnecessary temp vars, and function calls, on hot paths. So this seems to be the best we can do without incurring runtime cost.
2025-11-20 15:52:49 +00:00
overlookmotel b2de90b501 refactor(linter/plugins): remove | undefined from optional function params types (#15885)
`| undefined` in optional function params (e.g. `param?: string | undefined`) is pointless. Remove them.
2025-11-19 20:30:24 +00:00
overlookmotel fa1780a25c refactor(linter/plugins): bundle deserializer (#15873)
Bundle the deserializer. Originally it wasn't bundled at my request, so that source maps wouldn't be required for debugging. But it's turning out that we have no problems in the deserializer - our problems are elsewhere!
2025-11-19 13:35:28 +00:00
overlookmotel 6b5205c065 feat(linter/plugins): implement deprecated SourceCode#getJSDocComment method (#15653)
Add `SourceCode#getJSDocComment` method. It currently throws an "unimplemented" error.

This concludes the `Context` API additions. Every method, getter, and property listed in ESLint's [custom rules docs](https://eslint.org/docs/latest/extend/custom-rules) are now present in our API too.

Note: `Plugin` and `Rule` types may still be missing properties.
2025-11-12 23:15:13 +00:00
overlookmotel 5eccff1417 feat(linter/plugins): implement deprecated SourceCode tokens methods (#15645)
Implement the following deprecated `SourceCode` methods, related to tokens:

* `getTokenOrCommentBefore`
* `getTokenOrCommentAfter`
* `isSpaceBetweenTokens`

The 1st 2 are currently unimplemented, but can be implemented when we add tokens APIs.

`isSpaceBetweenTokens` implementation is flawed at present, in the same way as `isSpaceBetween` is, due to lack of tokens. But probably close enough to be useful in many cases. Added tests which demonstrate the flaw.
2025-11-12 23:08:26 +00:00
overlookmotel 104a092ef5 refactor(linter/plugins): add unimplemented Context#markVariableAsUsed method (#15637)
Add `Context#markVariableAsUsed` method. It's unimplemented at present, so just throws an error saying "unimplemented". But that's better for user than "TypeError: context.markVariableAsUsed is not a function", and having the stub in codebase reminds us we need to implement it.
2025-11-12 16:50:03 +00:00
overlookmotel c4abee47e1 refactor(linter/plugins): move location-related types into location.ts (#15634)
Pure refactor.

Move TS type defs for location-related types into `location.ts`. `types.ts` is too crowded.

Also add JSDoc comments to these type defs.
2025-11-12 13:59:37 +00:00
overlookmotel 222a8f015a feat(linter/plugins): implement SourceCode#isSpaceBetween (#15498)
Implement `sourceCode.isSpaceBetween`. Implementation is not quite correct, because we don't have tokens yet. But it's better than no implementation at all. We can correct it once we have tokens.

The reason it's not correct is explained in comment on `isSpaceBetween` implementation, and the incorrect behavior is demonstrated in the test fixture.
2025-11-08 19:14:50 +00:00
overlookmotel 444ebfd497 perf(linter/plugins): use single object for parserServices (#15378)
Follow-on after #15364. Re-use a single object for `parserServices`, instead of creating a new object each time. This is better for perf, and also matches ESLint where each access to `sourceCode.parserServices` in a `create` invocation results in the same object.
2025-11-06 15:12:21 +00:00
overlookmotel fcb32ffd74 refactor(linter/plugins): use Record types (#15379)
Pure refactor. We were using a mix of `{ [key: string]: T }` and `Record<string, T>`. Standardize on `Record`.
2025-11-06 15:05:26 +00:00
magic-akari cc403f5216 fix(linter/plugins): Return empty object for unimplemented parserServices (#15364)
## Summary

- Changed `sourceCode.parserServices` from throwing an error to
returning an empty object `{}`
- Aligns with ESLint specification for parsers that don't implement
parser services
- Prevents crashes in ESLint plugins such as
`eslint-plugin-better-tailwindcss`.

## Motivation

According to the [ESLint custom parser
documentation](https://eslint.org/docs/latest/extend/custom-parsers#parseforeslint-return-object):

> services can contain any parser-dependent services (such as type
checkers for nodes). The value of the services property is available to
rules as context.sourceCode.parserServices. **Default is an empty
object.**

The current implementation throws an error when `parserServices` is
accessed, which breaks plugins that use defensive checks like:

```typescript
if(typeof ctx.sourceCode.parserServices?.defineTemplateBodyVisitor === "function"){
  // ...
}
```

Plugin authors defensively handle:
- ✅ `parserServices` being undefined
- ✅ `defineTemplateBodyVisitor` not existing
- ❌ `parserServices` getter throwing an error (unexpected)

## Solution

Return an empty object `{}` for unimplemented parser services, which:
1. Matches ESLint's documented default behavior
2. Allows plugins' defensive checks to work correctly
3. Provides correct semantics for "not implemented"

## Test Plan

- Existing tests should continue to pass
- Plugins using defensive checks like the example above will no longer
crash

## Related

Fixes the compatibility issue with plugins like
[eslint-plugin-better-tailwindcss](https://github.com/schoero/eslint-plugin-better-tailwindcss/blob/a90cc3f547098a4293d7b0b7998bf6774f717cd2/src/utils/rule.ts#L140)

---------

Signed-off-by: magic-akari <akari.ccino@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: overlookmotel <theoverlookmotel@gmail.com>
2025-11-06 14:44:04 +00:00
overlookmotel 4c0ba92e77 perf(linter/plugins): use singleton object for ScopeManager (#15332)
Follow-on after #14890.

That PR implemented `ScopeManager` class. Instead of a class, which an instance of is created for each file, use a singleton object, with the underlying TS-ESLint scope manager stored in a top-level var. Methods/getters of `SCOPE_MANAGER` lazily initialize the TS-ESLint scope manager as required, and cache it.

This pattern is the same as we use for `SourceCode`, enabled by the fact that only 1 file is linted at a time. This has several advantages:

1. Reduce object creation - less GC pressure.
2. Property accesses don't need to go up prototype chain, as they would for instances of a class.
3. No need for private properties, which are somewhat expensive to access.
2025-11-05 16:23:52 +00:00
overlookmotel 69e61d46bd docs(linter/plugins): update comment (#15293)
Just update a comment to add "Reduce object creation" to list of advantages of a singleton object.
2025-11-05 10:26:16 +00:00
Arsh f7bef7325f feat(linter/plugins): scope manager API (#14890)
- "For now" implementation of #14827
- Uses `@typescript-eslint/scope-manager` which works thanks to recent
efforts to align JS side AST with ESTree.
- `@typescript-eslint/scope-manager` is well-built: It is factored out
from `ts-eslint` neatly, and is pure scope analysis with minimal
dependencies. Tsconfig resolution and syntax parsing is done by its
dependents.
### Tasks
- [x] `ScopeManager` class instance at `sourceCode.scopeManager`
- [x] Direct usage APIs: `sourceCode.isGlobalReference`,
`sourceCode.getDeclaredVariables`, `sourceCode.getScope`,
~~`sourceCode.markVariableAsUsed`~~
  - [x] Tests
- [x] Limit API surface. We want maneuverability for our eventual
implementation, so we should limit the exposed methods to the only two
methods documented as non-deprecated: [ScopeManager |
ESLint](https://eslint.org/docs/latest/extend/scope-manager-interface#acquirenode-inner--false:~:text=ScopeManager%20interface-,Fields,getDeclaredVariables(node),-Deprecated%20members).
- Limiting other interfaces will probably need monkey patching; only
public types are limited instead.
### ScopeManager API real world use examples
-
[`eslint-plugin-import`](https://github.com/import-js/eslint-plugin-import/blob/01c9eb04331d2efa8d63f2d7f4bfec3bc44c94f3/src/rules/no-import-module-exports.js#L20)
-
[`eslint-plugin-react`](https://github.com/facebook/react/blob/71b3a03cc936c8eb30a6e6108abf5550f5037f71/packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts#L149)
-
[`eslint-plugin-vue`](https://github.com/vuejs/eslint-plugin-vue/blob/917787c46a268b1910b9c77e091656a428d70a89/lib/utils/scope.js#L14)
-
[`eslint-plugin-svelte`](https://github.com/search?q=repo%3Asveltejs%2Feslint-plugin-svelte%20scopeManager&type=code)

---------

Signed-off-by: Arsh <emmagoldmanvevo+github@pm.me>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
2025-10-29 10:53:35 +00:00
leaysgur a8e5181ab7 chore(infra): dogfooding oxfmt (#14979)
Fixes #14803

- Added `oxfmt` and `oxfmtrc.jsonc`
- Remove `dprint` plugin and useless ignore paths
- Apply `dprint` again
- Apply `oxfmt`
2025-10-28 01:16:46 +00:00