Commit Graph

63 Commits

Author SHA1 Message Date
Boshen 1193c9dbaf chore(oxfmt): dogfood operator position at line start (#25937)
dogfood
2026-08-20 14:36:47 +00:00
camc314 9d4592c72f fix(linter): reject duplicate JS plugin names (#25243)
## Summary
- reject JS plugins whose normalized names collide
- keep registered plugin names isolated per LSP workspace
- add a CLI regression fixture covering two plugins that expose the same rule name

Fixes #25164
2026-08-03 09:40:27 +00:00
konh f302f4131e fix(linter): normalize oxlint jsPlugin prefixes (#23335)
Closes #22131

## Summary

- normalize `oxlint-plugin-*` names the same way as existing
`eslint-plugin-*` names
- keep Rust `normalize_plugin_name` and JS `normalizePluginName`
behavior in sync
- reject `oxlint-plugin-*` aliases as non-normalized plugin names
- add JS plugin fixtures for package-name, `meta.name`, and scoped
package normalization

## AI Assistance

This PR was prepared with AI assistance. I reviewed the generated
changes and test results, and I am responsible for the contribution.

Co-authored-by: xiaokhkh <10765730+xiaokhkh@users.noreply.github.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
2026-06-15 10:46:58 +01:00
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 df6817e9a1 refactor(linter/plugins): add hasSuggestions to RuleDetails (#18962)
Store and validate `rule.meta.hasSuggestions`. This is a pre-condition for supporting suggestions.
2026-02-05 10:47:50 +00:00
overlookmotel 88f30e0b4a feat(linter/plugins): move eslint compatible plugin conversion to eslintCompatPlugin function (#18791)
Closes #18611.

Previously `definePlugin` and `defineRule` were doing double duty:

1. Providing type safety (standard `defineConfig` pattern).
2. Converting a plugin/rule to be ESLint compatible

Combining the two duties is probably confusing to users. Users are familiar with the `defineConfig` pattern and these functions generally don't do anything at runtime - just require a specific type as param and return the input untouched.

This PR adds a separate function `eslintCompatPlugin` which does the conversion to an ESLint-compatible plugin.

`definePlugin` and `defineRule` are now no-ops at runtime - they just pass through the plugin/rule unchanged.
2026-02-01 12:37:48 +00:00
camc314 3f43d4cb53 fix(linter): accept bools as valid values for fixable (#18772)
fixes https://github.com/oxc-project/oxc/issues/17967
fixes https://github.com/oxc-project/oxc/issues/18700
closes https://github.com/oxc-project/oxc/pull/18011

This PR allows boolean values as valid options for meta.fixable. While this technically doesn't align exactly with eslint, it reduces some friction about getting users to migrate from exlint to oxlint, by increasing the number of plugins that are compatible.
2026-01-31 16:27:20 +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 6c5a09fb06 refactor(linter/plugins): remove Workspace and WorkspaceIdentifier types (#18757)
Pure refactor. Remove these 2 types which were both just aliases for `string`.

In Rust type wrappers can be useful (e.g. `ScopeId`) but in TS they don't add type safety, and IMO make the code less readable.
2026-01-31 16:09:27 +00:00
overlookmotel 005910a084 fix(linter/plugins): support plugins outside of workspace (#18755)
## The problem

### 1. Bug with plugins outside of workspace directory

There was a bug in workspaces implementation. `loadPlugin` was assuming that the plugin file is within the workspace, but that's not necessarily the case. e.g.:

* Workspace 1: `/path/to/project/packages/foo`
* Workspace 2: `/path/to/project/packages/bar`
* JS plugin: `/path/to/project/node_modules/eslint-plugin-whatever`

This problem arises because `loadPlugin` is determining which workspace to load a plugin for by search for the workspace whose root directory contains the plugin file.

### 2. Performance

Every call to `loadPlugin`, `setupRuleConfigs`, or `lintFile` was first searching through all workspaces to find the right one (`getResponsibleWorkspace`).

This is really expensive when there are lots of workspaces, and quite expensive even when there's only 1 because iterating hashsets is relatively slow.

## This PR

This PR separates out CWD and workspace identifiers into 2 different concerns.

* Each workspace has an ID which is unique.
* Each workspace has a CWD (path to root directory as a file path).

The workspace ID is currently the workspace's root directory as a `file://...` URL. Using that as it's guaranteed unique, even for paths which are not valid UTF-8 (`Path::to_string_lossy` can produce the same `&str` for different paths). But ID it doesn't have to be the URI - we could change it later to an integer ID if we want.

Rust-side code passes the workspace ID to every call to `loadPlugin`, `setupRuleConfigs`, or `lintFile`.

In CLI, it just passes `null` as the ID. There's only ever 1 workspace in CLI, so no point in the expense of passing a `String` on every call.

These functions look up the CWD from the workspace ID in the `cwds` map.

Separating these 2 concerns solves both problems listed above:

1. Plugins can be outside of the workspace's CWD (see added test).
2. `getResponsibleWorkspace` is removed, solving the perf issue.

Note: The multiple hashmap lookups on each call to `lintFile` remain a perf drain. This is addressed in a later PR in this stack.
2026-01-31 15:27:30 +00:00
Sysix 5bd67583d4 refactor(linter/plugins): add napi function createWorkspace and destroyWorkspace (#17809)
This PR introduces workspace isolation for the oxlint NAPI plugin system, adding `createWorkspace` and `destroyWorkspace` functions to manage multi workspace support for the language server.

This does not fully solve the integration, but should be a good start for all future work. One example is the buffer for multiple calls at the same time (?).

Added a new workspace isolation layer that uses the current working directory (cwd) as the workspace identifier:

- `createWorkspace(cwd:  string)`: Initializes a new isolated workspace for plugins and configurations
- `destroyWorkspace(cwd: string)`: Cleans up workspace resources, unloading all associated plugins and options
- `getResponsibleWorkspace(filePath: string)`: Determines which workspace is responsible for a given file path, reflecting the changes in https://github.com/oxc-project/oxc/pull/17853
2026-01-30 13:52:44 +00:00
overlookmotel 2acf5686b1 fix(linter/plugins): keep Infinity in rule default options (#18550)
Rules' default options are serialized to JSON and then `JSON.parse`-ed again, to ensure they don't contain cyclic references.

In the process, `Infinity` was converted to `null` (because JSON can't encode `Infinity`). This broke one of `eslint-plugin-stylistic`'s rules, which has an option which defaults to `Infinity`.

Fix this by replacing `Infinity` with a placeholder string in `JSON.stringify` and then replacing it before `JSON.parse`.
2026-01-25 21:31:27 +00:00
renovate d4aeb0982c chore(deps): update dependency oxlint to v1.39.0 (#17924)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Adoption](https://docs.renovatebot.com/merge-confidence/) | [Passing](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|---|---|
| [oxlint](https://oxc.rs) ([source](https://redirect.github.com/oxc-project/oxc/tree/HEAD/npm/oxlint)) | [`1.38.0` → `1.39.0`](https://renovatebot.com/diffs/npm/oxlint/1.38.0/1.39.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/oxlint/1.39.0?slim=true) | ![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/oxlint/1.39.0?slim=true) | ![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/oxlint/1.38.0/1.39.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/oxlint/1.38.0/1.39.0?slim=true) |

---

### Release Notes

<details>
<summary>oxc-project/oxc (oxlint)</summary>

### [`v1.39.0`](https://redirect.github.com/oxc-project/oxc/compare/oxlint_v1.38.0...oxlint_v1.39.0)

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxlint_v1.38.0...oxlint_v1.39.0)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/oxc-project/oxc).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi43NC41IiwidXBkYXRlZEluVmVyIjoiNDIuNzQuNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2026-01-12 11:13:36 +00:00
overlookmotel 67f8c5d959 fix(linter/plugins): get correct plugin name in all cases (#17033)
A couple of changes to how plugin names are dealt with:

### Package names

Don't use package name if the specifier used to load the plugin is a relative path.

If your project is called `super-server`, and you load a plugin from `../plugins/no-foo.js`, that plugin shouldn't be called `super-server` - that's the name of the project, not the plugin.

Now it throws an error, telling you to give the plugin a name.

### Ensure `context.id` matches the plugin name

Previously `context.id` might not match the actual plugin name (it could include e.g. `eslint-plugin-` prefix, for example). Now it always matches.

This required re-implementing `normalize_plugin_name` on JS side too.

### Tests

Add a bunch of tests for every combination I could imagine of `plugin.meta.name`, package name, and alias. Those tests make sure `context.id` is same as the plugin name on Rust side.

### Incomplete

There's 2 things remaining to get naming completely correct:

1. Support plugin names with slashes in them e.g. `@scope/eslint-plugin-name` (#14557).

2. It's possible for the same plugin to have different names in different places, because you can alias the same plugin twice with different names in a config, and an override. Plugin names (in `context.id`, and in `// oxlint-disable` comments) should reflect the plugin name *as it is in the config which enabled that rule*.

I'm leaving those 2 for now.
2025-12-18 08:58:33 +00:00
overlookmotel 8bc42876d9 feat(linter/plugins): validate options against schema (#16974)
Fixes #15631.

#16930 implemented options validation against `rule.meta.schema`, for the purpose of supporting default options added by schema. But it didn't actually do anything if validation failed.

Implement validation of options against schema, and pass any errors back to Rust to print.
2025-12-17 01:36:00 +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 23ac6b195d fix(linter/plugins): apply defaults from meta.schema to options (#16930)
ESLint has 2 different ways to define default options for a rule - `rule.meta.defaultOptions`, or `rule.meta.schema` containing a schema which defines default values.

We previously supported the former, but not the latter. This PR implements support for defining defaults via `rule.meta.schema`.

We compile the schema and apply it to options on JS side, using [`ajv`](https://www.npmjs.com/package/ajv), which is what ESLint uses. But this process only happens once before the start of linting, not once for every file, so the perf hit should be small.

This also prepares the way for validating options against the schema (#15631).
2025-12-16 23:03:45 +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 2f946cf639 fix(linter/plugins): error if defaultOptions is not JSON-serializable (#16959)
Throw an error if `rule.meta.defaultOptions` isn't JSON-serializable. This makes using `deepFreezeJsonArray` on it safe - can't lead to infinite loop if `defaultOptions` is self-referential.
2025-12-16 16:38:38 +00:00
overlookmotel d77e22d4d9 perf(linter/plugins): use DEFAULT_OPTIONS for rules with empty array as default options (#16913)
Small optimization to options merging. Treat rules which define `defaultOptions` as an empty array the same as those which provide no `defaultOptions` at all. This makes options merging take a faster path for these rules.
2025-12-15 17:14:11 +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 ab4deb0f13 fix(linter/plugins): improve safety of options merging (#16549)
Fix for an edge case.

Code for merging options was assuming that `defaultOptions` is a plain object with standard prototype, but that assumption doesn't actually hold. Alter the merging logic to ignore properties on prototype of objects in `defaultOptions`.
2025-12-06 01:31:24 +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 af3618f0cd style(linter/plugins): prefer const to let (#16385)
Use `const` where variables are not reassigned instead of `let`. Preparation for enabling ESLint's `prefer-const` rule (running as an Oxlint JS plugin) in our linting setup.
2025-12-02 10:06:17 +00:00
overlookmotel 5da1a6326d feat(linter/plugins): introduce RuleTester (#16206)
Part of #16018.

Implement `RuleTester` class for testing rules. Has same API as ESLint's `RuleTester`.

A few features not implemented yet:

* Fixes are not executed, or checked against provided `output`.
* Suggestions are not checked (we don't yet have support for suggestions in JS plugins).
* No use of config provided through `RuleTester` class constructor or via `RuleTester.setDefaultConfig`.
2025-11-30 23:51:14 +00:00
overlookmotel 78aa2944ea fix(linter/plugins): deep freeze options (#16218)
Deep freeze default options for rules, so that `context.options` is frozen.
2025-11-27 23:04:54 +00:00
overlookmotel c6245a4723 refactor(linter/plugins): add registerPlugin function (#16210)
Pure refactor. Move a few lines of code into `loadPlugin`, to leave a sync function `registerPlugin` which contains the logic for registering a plugin.

Required for #16206, which will call this method directly, skipping `import`-ing the plugin from a module.
2025-11-27 22:32:35 +00:00
overlookmotel be36e36c49 docs(linter/plugins): fix JSDoc comment for loadPluginImpl (#16211)
Fix description of errors that `loadPluginImpl` can throw in JSDoc comment.
2025-11-27 17:42:08 +00:00
overlookmotel cbb108aa1c feat(linter/plugins): support default options (#16170)
A step towards #15630. Where a rule provides default options, pass them to `create` function as `context.options`.

We don't yet have full options support (#14825), so this is incomplete. Once user can provide options for rules in config, those options need to be merged with default options.

Initially, we should probably do that merging on JS side (borrowing ESLint's code), and then later on port it to Rust.
2025-11-26 21:27:26 +00:00
overlookmotel 75249e06ec fix(linter/plugins): handle non-UTF8 file paths (#16157)
Fix an edge case bug.

Gracefully handle plugins or files being linted that have file paths which are not valid UTF-8. Previously these would cause panics.

* Load plugin: Convert plugin path to a `file://...` URL on Rust side, instead of on JS side. File URLs can encode any path via URL escaping.
* Lint file: Convert path to `String` with `to_string_lossy`.

Avoiding calling `pathToFileURL` on JS side should also be more performant, but that's a very marginal benefit, as loading plugins is a one-time cost in each lint run.

This adds a dependency on `url` crate. It's a very popular and battle-tested crate. 430m downloads. https://crates.io/crates/url

I could not work out how to add tests for this, as it seems it's not possible to create a file on Mac whose path is not valid UTF-8. It *is* possible on Linux and Windows, but no way to check such a file into Git without it malfunctioning on Mac. We could create test files when tests run, dependent on platform, but I didn't want to create complication in the test infra just for this edge case. It *should* work!
2025-11-26 17:16:54 +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 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 84d1f4fad1 perf(linter/plugins): downgrade some checks to debug-only (#15922)
These checks are "sanity checks" only. The inputs to these functions comes from Rust code which we control, and would be `debug_assert!` if this was Rust. So only run these assertions in debug build.
2025-11-21 13:40:32 +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 fbf0fd4daf docs(linter/plugins): add JSDoc comments to Plugin and Rule types (#15815)
Docs only change. Add JSDoc comments to `Plugin` and `Rule` types.
2025-11-17 21:52:33 +00:00
overlookmotel ac5e4b5e2c docs(linter/plugins): add JSDoc comments and improve comments (#15814)
Add JSDoc comments on the entry point functions passed to Rust, and improve some other comments.
2025-11-17 21:52:33 +00:00
overlookmotel 28ffcefa86 refactor(linter/plugins): improve rule validation error messages (#15812)
Remove ambiguity from error messages.
2025-11-17 21:52:33 +00:00
overlookmotel 61650f9baf refactor(linter/plugins): improve calculating plugin name (#15811)
Provide better errors when validating plugin names, correct type of `packageName` passed from Rust, and use cheaper check `packageName !== null`.
2025-11-17 21:52:33 +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 257360f94e fix(linter/plugins): fill in TS type def for RuleMeta (#15629)
Fill out the TS type def for `RuleMeta`, based on [ESLint's docs](https://eslint.org/docs/latest/extend/custom-rules).

Most of the properties can be ignored at runtime in Oxlint (they're primarily useful for generating docs), but this has also surfaced a few features we need to implement. These are marked with "TODO" comments, and I'll also open issues for them shortly.
2025-11-12 13:22:14 +00:00
overlookmotel ac95e7d8ec refactor(linter/plugins): make properties of RuleDetails immutable (#15481)
Types refactor. Mark properties of `RuleDetails` which are immutable after construction with `readonly`.
2025-11-08 13:04: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 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
Peter Wagenet 2de9f39a96 fix(linter/plugins): fall back to package name if meta.name is missing (#14938)
Fixes #14937
2025-10-25 23:45:50 +01:00
overlookmotel 26435a1ba9 perf(linter/plugins): small perf optimizations (#14420)
Follow-on after #14415 and #14416. Perf micro-optimizations.
2025-10-07 19:25:34 +00:00
camc314 529e88ee47 feat(linter/plugins): support messageIds (#14415) 2025-10-07 17:11:29 +00:00
overlookmotel 96663fbaab fix(linter/plugins): do not call before hook if empty visitor (#14401)
If rule is defined using `createOnce`, and it returns an empty visitor (`before` / `after` hooks only), then don't run the hooks - the rule is a no-op.

Reason this is important is forward-compatibility with an optimization we could make where we calculate on Rust side whether AST contains any nodes that rules act on, and if it doesn't, skip calling into JS entirely. In that case, the `before` hook wouldn't run on those files.

We can't emulate that behavior entirely, but we can at least avoid users creating rules which *only* contain a `before` hook, and then finding that when we apply that optimization later on, it stops behaving as expected.

i.e. we make the `before` hook never be called for a rule defined like this:

```js
export default {
  createOnce(context) {
    return {
      before() {
        if (content.filename.endsWith('.tsx') {
          context.report({ message: 'No TSX!', node: context.sourceCode.ast });
        }
      },
    };
  },
};
```
2025-10-07 09:43:21 +00:00
camc314 52f04bd09c fix(linter): use pathToFileURL for importing plugins to ensure correct URL format (#14394)
writing a test for this is too hard

fixes https://github.com/oxc-project/oxc/issues/14375
2025-10-06 23:15:24 +00:00
overlookmotel 257506582d perf(linter/plugins): store if rule is fixable as boolean (#14205)
Small optimization. Store if a rule is fixable as a single `isFixable` boolean, instead of putting the whole `RuleMeta` object in `Context`.

This is more efficient on memory, and a boolean is faster to check as it's stored inline in the object, as opposed to strings which are stored as a reference to another object on the heap.
2025-09-28 18:29:33 +00:00