Commit Graph

29 Commits

Author SHA1 Message Date
overlookmotel 9de9385fd1 refactor(linter/plugins): rename oxlint/plugin to oxlint/plugins (#18792)
Part of #18610.

Our intent is to move `definePlugin`, `defineRule`, and `eslintCompatPlugin` to a separate package `@oxlint/plugins` (plural to avoid confusion with naming convention for plugins `oxlint-plugin-xxx`).

So rename the current entry point which exports these functions from `oxlint/plugin` to `oxlint/plugins` (plural).
2026-02-01 13:23:14 +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
overlookmotel 6ad44809e6 refactor(linter/plugins): remove experimental warning (#18771)
As we prepare for alpha release of JS plugins, it feels like we can remove the warning logged to stderr.

```
WARNING: JS plugins are experimental and not subject to semver.
Breaking changes are possible while JS plugins support is under development.
```

They do still remain experimental, but I don't think we need to shout that quite as loudly now.
2026-01-31 17:28:11 +00:00
camc314 81eb6fa9db refactor(linter): split entry points for custom plugins/config/types (#18601)
This is technically breaking (only for types), but custom plugins are experimental.

The ruletester/defineRule/definePlugin are re-exported but with a `@deprecated` tag which I think is a good enough trade off

I want to avoid users bringing in a ton of custom plugin code when doing `import { defineConfig } from 'oxlint'`
2026-01-27 00:58:08 +00:00
connorshea 529901cfc9 fix(linter): include JS plugin rules when calculating total rule count (#17520)
This ensures that we print the expected number of rules for users that are using JS Plugins.

Fix #17519.

Based on #17517, because otherwise we'd end up with weird snapshot differences when trying to merge these anyway.

AI Disclosure: The fix here was generated with Copilot + GitHub's Raptor mini model.
2025-12-31 23:14:52 +00:00
connorshea 96ef2cced3 fix(linter): print total rule # when using a single nested config (#17517)
For a while, we've been skipping the printing of rule runs unless `--disable-nested-config` was passed. This was a result of the nested_configs value including the base config (see #16356). When we were checking whether that value was empty, it would always have at least 1 item (unless nested configs were entirely disabled).

As part of this, I've updated some configs in the fixtures to disable correctness rules, otherwise we will end up with a bunch of fixture snapshots needing updates every time a new default rule is added.

It's probably easiest to review this one commit at a time.
2025-12-31 22:56:47 +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 690abec235 test(linter/plugins): use subpath imports (#15920)
Shorten imports in test fixtures using [subpath imports](https://nodejs.org/docs/latest-v24.x/api/packages.html#subpath-imports).

Before:

```ts
import type { Plugin } from '../../../dist/index.js';
```

After:

```ts
import type { Plugin } from '#oxlint';
```
2025-11-21 11:44:25 +00:00
overlookmotel ca7a6d3d79 test(linter/plugins): automate testing all fixtures (#15911)
Remove the need to list every fixture in `e2e.test.ts` file by automatically running tests on every fixture in `test/fixtures` directory.

Running Oxlint with `--fix` and running fixtures with ESLint is now controlled by adding an `options.json` file to the fixture directory contining e.g. `{ "fix": true }` or `{ "eslint": true }`.

Now we can add a new fixture just by creating a new directory in `fixtures`.
2025-11-21 11:38:01 +00:00
overlookmotel def251dee0 test(linter/plugins): remove unused imports from fixtures (#15655)
Pure refactor to tests. These imports are no longer used. Remove them.
2025-11-12 23:49:48 +00:00
overlookmotel 075079cc9c test(linter/plugins): improve reliability of replacing paths in output (#15424)
When replacing absolute paths in fixture snapshots, replace paths anywhere in the output, rather than only in certain specified patterns.

Additionally, introduce `<fixtures>` and `<fixture>` replacements to shorten paths in snapshots.

```diff
- File path: <root>/apps/oxlint/test/fixtures/fixture_name/files/1.js
+ File path: <fixture>/files/1.js
- File path: <root>/apps/oxlint/test/fixtures
+ File path: <fixtures>
```

This allows removing the `relativePath` helper function from some test plugins, outputting paths (e.g. `cwd`) anywhere, and generally will avoid problems with snapshots being system-dependent, and dependent on directory layout of fixtures.

Also, amend ESLint `stdout` output in snapshots to align all rule names, to reduce churn in snapshots, and avoid mismatches between local and CI (presumably due to differences in terminal width).

```diff
- filename: <fixture>/files/1.js                              define-plugin-plugin/create-once
- filename: <fixture>/files/long_filename.js                              define-plugin-plugin/create-once
- filename: <fixture>/files/very_long_filename.js                              define-plugin-plugin/create-once
+ filename: <fixture>/files/1.js                                 define-plugin-plugin/create-once
+ filename: <fixture>/files/long_filename.js                     define-plugin-plugin/create-once
+ filename: <fixture>/files/very_long_filename.js                define-plugin-plugin/create-once
```
2025-11-07 12:28:48 +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
overlookmotel a2914fe8d8 feat(linter/plugins): add loc field getter to all AST nodes (#14355)
Add `loc` to all AST nodes.

`loc` is computed lazily, as it's quite an expensive operation - because it requires searching the source text for line breaks and building a translation table.

We can consider making `loc` a "real" property once we have a faster translation table built on Rust side.
2025-10-05 14:02:12 +00:00
overlookmotel 5505a86121 feat(linter/plugins): include range field in AST (#14321)
Add `range` field to AST nodes, using the support for `range` in raw transfer added in #14319.

Use `range` field rather than `start` + `end` in nodes passed to `context.report` and fixer methods - for closer alignment with ESLint.
2025-10-04 15:47:47 +00:00
overlookmotel 1387aaa495 test(linter/plugins): test createOnce returning no visitor functions (#14279)
Add a test to make sure `createOnce` can return an object with only `before` and `after` hooks (no visitor functions) and that both hooks are called for each file in this case.
2025-10-01 14:38:31 +00:00
overlookmotel acd1266d60 feat(linter/plugins): oxlint export types (#14163)
`oxlint` package export TS types. `defineRule` and `definePlugin` now give you type-safety and intellisense.

Writing plugins in TS is only supported on versions of NodeJS which support type-stripping. Have not added a library (e.g. Jiti) to support older NodeJS versions.

Probably some of the types are wrong. But I think this is a decent start.

"Are the types wrong" (added in #14177) is reporting an error in `index.d.ts`:

```ts
import { VisitorObject as Visitor } from "../dist/generated/visit/visitor.js";
//                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```

because the file in `dist/generated/visit` is actually called `visitor.d.ts` (`.d.ts`, not `.js`).

I'm not sure if that's a mistake in Rolldown's bundling, "Are the types wrong" being too strict, or a genuine problem.

But regardless, TS seems to pick up the types from `visitor.d.ts`, so I think we can probably merge this and look into / fix it later.
2025-09-28 14:59:48 +00:00
overlookmotel 8988d6408f test(linter/plugins): add line breaks to plugins files (#14181)
Style change only. Add line breaks to make the code easier to read.
2025-09-27 10:35:51 +00:00
overlookmotel aca083a8dc test(linter/plugins): include stderr output in snapshots (#14155)
Include `stderr` output in test snapshots. This makes sure that the "JS plugins are experimental" warning is not output if user doesn't use JS plugins.
2025-09-26 13:34:52 +00:00
overlookmotel 0029b7f218 test(linter/plugins): normalize line breaks in snapshots (#14154)
Trim leading/trailing line breaks from `stdout` in test snapshots and convert `\r\n` to `\n`. This ensures tests will pass on Windows, and removes extraneous line breaks from snapshots.
2025-09-26 12:31:19 +00:00
overlookmotel b4d716fd72 feat(linter/plugins): move custom JS plugin config to jsPlugins (#14133)
Fixes #12432.

JS plugins were previously configured by adding them to `plugins` array in config file. Instead, move them to a separate `jsPlugins` array.

Before:

```json
{
  "plugins": ["oxc", "react", "./custom-plugin.js"],
}
```

After:

```json
{
  "plugins": ["oxc", "react"],
  "jsPlugins": ["./custom-plugin.js"],
}
```
2025-09-26 10:06:36 +00:00
overlookmotel fc14abc5b5 test(linter/plugins): format test fixtures (#14125)
Pure refactor. Format files in `test/fixtures` directory. Only changes to fixture files are to add `// dprint-ignore-file` comments to 2 of them.
2025-09-25 20:35:48 +00:00
overlookmotel a6f965f1a2 test(linter/plugins): simplify configs in test fixtures (#14124)
Pure refactor. Remove `"ignorePatterns": ["**/*", "!files/**"]` from `.oxlintrc.json` in every test fixture, by running `oxlint` with `oxlint --js-plugins files`.
2025-09-25 20:35:48 +00:00
overlookmotel b1685f7a6a test(linter/plugins): refactor tests (#14123)
Refactor tests for Oxlint plugins. Break up snapshots into 1 per fixture directory, to make them easier to navigate.
2025-09-25 20:35:47 +00:00
overlookmotel 788e495686 test(linter/plugins): improve ESLint compat tests (#14119)
Adding `loc` to nodes should not be required for plugins to work in ESLint (and it isn't). Remove this from the test cases.
2025-09-25 13:36:58 +00:00
overlookmotel 5c862f95ae test(linter/plugins): standardize test fixture structure (#14114)
Pure refactor of tests. Standardize the directory structure of all test fixtures.

* Move files to be linted into `files` subdirectory.
* Move plugin to a file called `plugin.js` (except 1 fixture which has multiple plugins).
* Change `ignorePatterns` in `.oxlintrc.json` config files to whitelist files in `files` dir, instead of blacklisting the plugin file.
2025-09-25 12:09:54 +00:00
overlookmotel 314c27de60 fix(linter/plugins): definePlugin apply defineRule to rules (#14065)
`definePlugin` previously passed the input through unchanged. This necessitated wrapping every rule in `defineRule` too.

Instead, make `definePlugin` call `defineRule` for every rule.

Before:

```js
import { definePlugin, defineRule } from 'oxlint';

export default definePlugin({
  meta: { name: 'my-plugin' },
  rules: {
    'my-rule': defineRule({
      createOnce: (context) => ({ /* visitor */ }),
    }),
    'my-other-rule': defineRule({
      createOnce: (context) => ({ /* visitor */ }),
    }),
  },
});
```

After:

```js
import { definePlugin } from 'oxlint';

export default definePlugin({
  meta: { name: 'my-plugin' },
  rules: {
    'my-rule': {
      createOnce: (context) => ({ /* visitor */ }),
    },
    'my-other-rule': {
      createOnce: (context) => ({ /* visitor */ }),
    },
  },
});
```
2025-09-24 00:25:08 +00:00
overlookmotel 2fd4b1ebd3 test(linter/plugins): rename test (#14064)
Pure refactor. Just rename a test. Makes for a less messy diff on following PR #14065.
2025-09-24 00:03:42 +00:00
overlookmotel f47f9788bc feat(linter/plugins): add definePlugin API (#14008)
Export a method `definePlugin` from `oxlint` package, to go with `defineRule`. Used as follows:

```js
import { definePlugin, defineRule } from 'oxlint';

const myRule = defineRule({
  create(context) { /* rule impl */ },
});

export default definePlugin({
  meta: { name: "my-plugin" },
  rules: {
   'my-rule': myRule,
  },
});
```

At present this method doesn't have much utility, as we don't export TS types for it yet, but it's part of the intended API, so may as well add it now.
2025-09-22 20:22:54 +00:00
overlookmotel 3e117c65d7 feat(linter/plugins): add defineRule API (#13945)
Add `defineRule` method for defining rules. This is the 2nd part of the "alternative API" mentioned in https://github.com/oxc-project/oxc/issues/9905#issuecomment-3208424180.

```js
import { defineRule } from 'oxlint';

export default defineRule({
  meta: { /* ... */ },
  create(context) {
    // Rule implementation
  }
});
```

or, using the alternative `createOnce` API:

```js
export default defineRule({
  meta: { /* ... */ },
  createOnce(context) {
    // Rule implementation
  }
});
```

When using `createOnce`, `defineRule` creates a `create` method and adds it to the rule object. So the rule remains ESLint-compatible, while getting a potential speed boost when the rule is used with Oxlint. This PR includes a test that runs an Oxlint plugin that uses `defineRule` + `createOnce` in ESLint.

Not done yet: We need to export TS type defs, so the type checking benefit of `defineRule` is realized. I'll do that in a later PR.

---

`defineRule` is intended to be used when defining JS plugins, so we should move it into a separate NPM package (`oxlint-tools`?), so that someone publishing a plugin to NPM doesn't need their plugin to have a dependency on `oxlint` itself, which includes large binaries.

But for now, for simplicity, just include it in `oxlint` package.
2025-09-22 20:22:54 +00:00