Commit Graph

81 Commits

Author SHA1 Message Date
overlookmotel 0861d9a5c8 fix(linter/plugins): remove getters from Context (#20115)
Fix an incompatibility with `eslint-plugin-jsdoc`. It relies on properties of `context` being values rather than getters:

https://github.com/gajus/eslint-plugin-jsdoc/blob/26276d428631a2625575607cba7875dab6797091/src/buildForbidRuleDefinition.js#L86-L103

When `options` property is a getter, this code throws an error because it attempts to define a property with both `value` and `get` in its descriptor.

This PR replaces the `id` and `options` getters with value properties.

The getters were there to prevent the `Context` object from being mutated by rule code, and allow the object to be frozen, as it is in ESLint. But each rule gets its own `Context` object, so if it chooses to mutate it, it only harms itself, and can't impact other rules/plugins. In ESLint, trying to alter the frozen `Context` object would be a hard error, so it's doubtful that any ESLint plugins do this anyway.

This is no real downside to this change for us, so we might as well make the change, and fix `eslint-plugin-jsdoc` and any other plugins which may depend on ESLint's behavior here.
2026-03-08 17:27:52 +00:00
overlookmotel d96d26d7a8 fix(linter/plugins): provide parser.version (#19364)
Add correct version for `context.languageOptions.parser.version`.

Blocker previously was that version should match `oxc-parser` package, which is not entirely trivial, as we release NAPI packages and Oxlint separately.

Work around this by giving `"oxlint"` as name of the parser, so we can use Oxlint version.

TSDown extracts the `version` field from `package.json` without including the whole contents of `package.json` in the bundled code.
2026-02-13 11:20:26 +00:00
overlookmotel f33248da63 refactor(linter/plugins): move and refactor handling legacy report call forms (#19239)
Follow on after #19193. Refactor.

That PR introduced support for legacy call styles for `context.report`.

Move that implementation into `report.ts`, shorten the code a little, and produce consistent object shapes. Avoid calling `convertLegacyCallArgs` in the common case that `report` is called in the modern way.
2026-02-10 23:55:44 +00:00
camc314 d07059ce0f fix(linter/plugins): support legacy context.report(node, ...) calls (#19193)
fixes #19192
2026-02-10 09:50:38 +00:00
overlookmotel 6b32cc8dc9 refactor(linter/plugins): correct outdated comments (#18948)
Just update 1 outdated TODO comment, and delete another.
2026-02-04 15:10:15 +00:00
overlookmotel 986222470f perf(linter/plugins): reduce cost of workspaces (#18758)
Workspaces support (#17809) added overhead on JS side of hashmap lookups. Every call to `lintFile` has to look up details (e.g. registered rules, rule options) from hashmaps keyed by workspace URI.

### Perf

Take a different approach which is optimized for common case of there being only 1 workspace:

* At any time there's 1 "current workspace".
* Store rules, options, and CWD in top-level vars (back to how it was before #17809).
* Switch workspaces only when required. Switching reassigns the values in these top level vars.

Before:

* Get rules, options, and CWD from hashmaps keyed by workspace URI.

After:

* Quick check "is the current workspace the one we need?".
* If so, do nothing - the top-level vars `registeredRules`, `allOptions`, and `cwd` elready contain the right settings for the desired workspace.
* If current workspace is not the right one, look up the details for desired workspace in `workspaces` hash map and write them into the top-level vars.

In CLI, `workspaceUri` passed to `lintFile` etc is `null`, so the quick "in the right workspace?" check is particularly cheap - just `null === null`. So this reduces the overhead that workspace support adds to CLI to just that 1 instruction.

Add of these functions are entirely synchronous, so there's no possibility of a race where 1 call switches out the values in the top-level vars, while another call is busy using them.

### CLI: No call to `createWorkspace`

CLI now doesn't need to call `createWorkspace` at all. The "are we in right workspace?" check always succeeds, so it's irrelevant whether a workspace has been created or not - `workspaces` hashmap is never accessed.

So workspaces is now an LSP-only thing.

### Bundle

This PR also removes all imports from other files from `src-js/workspace/index.ts`. It now only imports `debugAssert`, which is optimized out in release build., leaving no imports at all.

Previously, the imports from other files were causing most of the JS plugins code to get loaded on first call to `createWorkspace`, which broke the lazy loading optimization - Oxlint CLI and LSP were both loading all the JS plugins code eagerly even if the user doesn't use JS plugins.

With this change, the lazy loading optimization is restored. This is visible in the conformance snapshot change in this PR.
2026-01-31 16:20:08 +00:00
overlookmotel 3818486b60 refactor(linter/plugins): cwd set per file (#18701)
Pass `cwd` into `setupFileContext`, so it can be set per file, rather than being a global setting.

This is preamble to #17809. This PR is some of the changes from that one split off, so the diff on #17809 is focused just on that one change.

This PR does nothing in itself, as currently `process.cwd()` is always passed to `setupFileContext`.
2026-01-30 01:13:35 +00:00
overlookmotel 2e1fbc2238 fix(linter/plugins): implement context.parserPath (#18644)
Closes #18119. Implement `context.parserPath`. It evaluates to `undefined`, same as what ESLint does by default.
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 2cc6ad25db feat(linter/plugins): add ecmaFeatures to parserOptions (#18313)
Add `ecmaFeatures` property to `context.languageOptions.parserOptions`, with `globalReturn` and `impliedStrict` properties.

In normal operation, these are set according to the source type. In conformance build, they can be overridden by config.
2026-01-21 00:14:27 +00:00
overlookmotel d51cb6cb1e docs(linter/plugins): remove outdated comments (#18194)
Remove 2 outdated comments. #18089 introduced `commonjs` source type.
2026-01-18 21:44:00 +00:00
overlookmotel 3a0a190d2c fix(linter/plugins)!: prevent access to cwd in createOnce (#18081)
Supporting JS plugins in LSP requires that different workspaces have a different value for `Context#cwd`.

As a first step, prevent accessing `cwd` property, or calling `getCwd` method in `createOnce`. Later on, we can make `cwd` a dynamic property.
2026-01-16 15:49:07 +00:00
overlookmotel 5cc7c907d6 feat(linter/plugins): pass env from Rust to JS (#17291)
Pass `env` to JS plugins. JS plugins don't do anything with it yet, except allow the user to see it. But they will in a following PR.
2025-12-23 10:53:25 +00:00
overlookmotel 42603ba23b fix(linter/plugins): always define languageOptions.globals (#17008)
Previously `context.languageOptions.globals` was `null` if no globals defined in config. This was a slight incompatibility with ESLint, which doesn't define `globals` property at all on `languageOptions` in this case.

Instead make `languageOptions.globals` an empty object. This should make no practical difference from what ESLint does, but avoids errors in rules if they do something like this:

```js
if (languageOptions.globals !== undefined) {
  // This would throw if `globals` was `null` (as it used to be)
  const hasWindow = languageOptions.globals.window !== "off";
}
```
2025-12-17 14:26:58 +00:00
overlookmotel b9706446d3 refactor(linter/plugins): store rule name in RuleDetails (#16973)
Pure refactor. Store rule name in `RuleDetails` object. Therefore rule name doesn't need to be passed separately to `createContext`.
2025-12-17 01:35:59 +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
overlookmotel 58f6b003d3 test(linter/plugins): conformance tester set languageOptions.ecmaVersion (#16754)
In conformance tester, populate `context.languageOptions.ecmaVersion` from config/test case. Some rules alter behavior depending on `ecmaVersion`, so we need to set it to pass some of those tests.
2025-12-11 16:21:31 +00:00
camc314 2acd66ed42 style(all): enable eslint/curly lint rule (#16753)
built on https://github.com/oxc-project/oxc/pull/16749, we can use the linter to be consistent here
2025-12-11 14:49:48 +00:00
overlookmotel 4f9d17fe33 perf(linter/plugins): remove unnecessary check (#16750)
This check is unnecessary as in order to obtain `languageOptions`, you have to go through `context.languageOptions` getter which already does the check.

If user hold onto the returned `languageOptions` object and accesses `globals` on it in a tick in between linting files, they'll just get `null` anyway.
2025-12-11 13:09:28 +00:00
overlookmotel 886e938876 style(linter/plugins): use block for multi-line if statement (#16749)
Style nit.
2025-12-11 13:09:27 +00:00
Arsh 5c62c16e43 feat(linter/plugins): allow JS plugins to access globals (#16512)
- This PR implements a naive, inefficient solution to #16359.
- To establish current and expected behavior, this PR serializes
rust-resolved and -overridden globals once for every file.

## Future work
- [ ] Add test for globals from the `env` field. Implement support if
not currently supported.
- [ ] Optimization: we want to serialize `globals` for all files only
once at the start of the lint run.

## AI Usage
- This PR was drafted by Claude Sonnet 4.5 in Plan + Agent mode, then
manually reviewed and iterated on.

---------

Co-authored-by: overlookmotel <theoverlookmotel@gmail.com>
2025-12-06 23:08:19 +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 41129ab1dd feat(linter/plugins): implement languageOptions.parser (#16292)
Implement `context.languageOptions.parser`. The `parse` function currently throws an "unimplemented" error, but it's still useful to have the other properties available to plugins.
2025-11-30 12:57:39 +00:00
overlookmotel fa3b070026 refactor(linter/plugins): prepare for rule options (#16158)
Preparatory work for supporting rule options.

Store options for all rules in an `allOptions` array. Currently this array only contains a single value for empty options, but in future we'll populate it with options extracted from configs, sent over from Rust side.

Am adding this now, before full implementation (#14825), as it's required for rule tester.
2025-11-26 12:23:27 +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
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 102037372f ci(linter/plugins): lint apps/oxlint with oxlint (#15887)
2nd try at #14573. Run oxlint on oxlint. Linter inception.
2025-11-20 15:52:49 +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 2eec3f5daf refactor(linter/plugins): improve types for options (#15696)
Similar to #15695. `options` is serialized from JSON, so can only contain a limited set of values.
2025-11-14 12:48:25 +00:00
overlookmotel 53bf16ad61 refactor(linter/plugins): improve types for settings (#15695)
Improve types for `settings`. `settings` is deserialized from JSON, so can only contain a limited set of values.
2025-11-14 12:48:25 +00:00
overlookmotel cfabd09daa refactor(linter/plugins): make error message for languageOptions.parser getter consistent (#15644)
Nit. As the pedantic Mr Copilot pointed out in https://github.com/oxc-project/oxc/pull/15637#discussion_r2518858637, the pattern we use in error messages is "not implemented", not "is not implemented". Make the error message in `languageOptions.parser` getter consistent with this pattern.
2025-11-12 16:56:04 +00:00
overlookmotel d2ff4baab2 refactor(linter/plugins): move report function and diagnostic types to separate file (#15635)
Pure refactor. Move `report` function and `Diagnostic` types into a separate file. `context.ts` was getting too long.
2025-11-12 15:15:58 +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 0d52a5e3aa feat(linter/plugins): implement Context#parserOptions getter (#15632)
Implement `parserOptions` getter on `Context` object.

This getter is deprecated by ESLint, but we have decided to implement deprecated methods/getters, to support as many existing ESLint plugins as possible (see #15606).
2025-11-12 13:42:06 +00:00
overlookmotel 287a538ec8 feat(linter/plugins): implement Context#get* deprecated methods (#15606)
Implement 4 methods on `Context` which are present in ESLint, but deprecated.

* `getFilename`
* `physicalFilename`
* `getCwd`
* `getSourceCode`

After some discussion, @camc314 and I concluded that we should add these methods despite their deprecation. The reasons behind that decision are documented in a comment in this PR.
2025-11-12 09:32:17 +00:00
overlookmotel 2de35be346 refactor(linter/plugins): add comments on Context#extend (#15604)
Add comments on `extend` methods explaining why they're OK to use in `createContext`.
2025-11-11 17:55:47 +00:00
overlookmotel 6957fb9a39 fix(linter/plugins): do not allow access to Context#id in createOnce (#15489)
Revert #15447. It's easy to allow `Context#id` to be accessed in `createOnce` when running rule in Oxlint, but not possible to do in ESLint compat mode with current API, because `id` isn't a property of the rule, so `defineRule` doesn't know it.

We could support it by making `definePlugin` compulsory for ESLint interop, and get it to set `id` property on `Context` objects properly.

But that's a bit tricky, so just disable access for now. It's probably not very useful anyway - a rule should know what its own name is!
2025-11-08 14:46:46 +00:00
overlookmotel 740963051d fix(linter/plugins): allow access to cwd in createOnce in ESLint interop mode (#15488)
#15301 made `Context#cwd` available in `createOnce`. Make it available in ESLint compat mode too. Ditto the `extend` method added in #15477.

Define all other `Context` methods/getters on prototype of `Context` in ESLint compat mode, for completeness.
2025-11-08 14:46:45 +00:00
overlookmotel 2f9735dcd8 feat(linter/plugins): implement context.languageOptions (#15486)
Implement `Context#languageOptions`.
2025-11-08 14:18:54 +00:00
overlookmotel c96ce29007 refactor(linter/plugins): rename RuleAndContext to RuleDetails (#15480)
Pure refactor. Give this type a better name. It now contains a lot more than just the `Rule` and `Context`.
2025-11-08 13:04:54 +00:00
overlookmotel bc731ff361 feat(linter/plugins): stub out all Context APIs (#15479)
Add stubs for all methods / getters on `Context` which aren't implemented yet. All throw an "unimplemented" error at present.
2025-11-08 13:04:54 +00:00
overlookmotel 50d295343f refactor(linter/plugins): add JSDoc comments to Context (#15478)
Add JSDoc comments to `Context`, so they appear in `.d.ts` file.
2025-11-08 13:04:53 +00:00
overlookmotel 5822cb409d feat(linter/plugins): add extend method to FILE_CONTEXT (#15477)
`extend` method exists on ESLint's `FileContext` type. Replicate it here (copying impl from ESLint).
2025-11-08 13:04:53 +00:00
overlookmotel a17ca32399 fix(linter/plugins): replace Context class (#15448)
Fix #15325.

Some ESLint rules create a wrapped copy of `context`, in order to patch `report` and pass that wrapped context object to another rule.

Some rules also rely on unspecified details of ESLint's implementation - that `context` is formed of 2 layers - top layer being `RuleContext` (`id`, `report` etc), and bottom layer being `FileContext` (`filename`, `sourceText` etc).

This usage was broken in our implementation because:

1. We collapsed the 2 layers into 1.
2. We used a `Context` class which stored its internal data in a private property. When `context.report` was called with a different `this`, it failed because the private property couldn't be accessed from an object which isn't an instance of the class.

Fix this problem by:

1. Splitting `Context` into 2 layers like ESLint does.
2. Not using classes or private properties.
3. No methods/getters on context objects use `this`.

We now have:

* Separate `Context` object per rule, containing properties related to the rule - a plain object which uses data from closure scope.
* All rule `Context`s inherit from the same singleton `FILE_CONTEXT` object, which provides getters for properties related to the file (same for all rules).

Tests adapted from #15326.
2025-11-07 19:43:57 +00:00
overlookmotel 981cbc59c9 refactor(linter/plugins): allow accessing context.id in createOnce (#15447)
Previously we blocked accessing `context.id` (rule name) in `createOnce`. There's no need to be so restrictive, as it's a static value, and not file-specific. Relax this restriction.
2025-11-07 19:43:56 +00:00
overlookmotel 8ec6a1ed49 refactor(linter/plugins): move settings-related code into separate file (#15401)
Pure refactor. Move this code into a separate file to keep it grouped together.
2025-11-07 00:06:32 +00:00
overlookmotel 9de1322765 perf(linter/plugins): lazily deserialize settings JSON (#15395)
Follow-on after #14724. Deserialize and deep freeze settings lazily on demand. These operations are not so expensive, but if settings object is large, they're not so cheap either. So avoid that work unless `context.settings` is accessed.

A better eventual solution will be to send all settings objects over to JS in one go after parsing all configs, rather than again and again for every file, but this is a simple optimization to get a bit of gain in the meantime.
2025-11-06 17:33:57 +00:00
overlookmotel fde753e023 fix(linter/plugins): block access to context.settings in createOnce (#15394)
Follow-on after #14724. Prevent accessing `context.settings` in `createOnce`. Settings are per-file, so they shouldn't be available in `createOnce`.
2025-11-06 17:33:57 +00:00