Skip to content

linter: introduce oxlint-plugin-eslint#19765

Closed
Copilot wants to merge 4 commits intomainfrom
copilot/introduce-oxlint-plugin-eslint
Closed

linter: introduce oxlint-plugin-eslint#19765
Copilot wants to merge 4 commits intomainfrom
copilot/introduce-oxlint-plugin-eslint

Conversation

Copy link
Contributor

Copilot AI commented Feb 26, 2026

Adds a new oxlint-plugin-eslint npm package that exposes all of ESLint's built-in rules as an Oxlint JS plugin, allowing users to use ESLint rules directly (e.g., as a hotfix when Oxlint's native implementation has a bug).

Usage

{
  "jsPlugins": ["oxlint-plugin-eslint"],
  "rules": {
    "eslint-js/no-unused-vars": "error"
  }
}

Plugin meta.name is eslint-js.

Changes

  • npm/oxlint-plugin-eslint/ — New package with dual ESM/CJS exports and eslint as a peer dependency.
  • apps/oxlint/src-js/plugin-eslint/index.ts — Plugin entry point; exports { meta: { name: "eslint-js" }, rules }.
  • apps/oxlint/scripts/generate-plugin-eslint.ts — Build-time codegen script that:
    • Scans eslint/lib/rules/ (excluding the registry index.js) to get all rule names.
    • Emits a CJS wrapper per rule in src-js/generated/plugin-eslint/rules/.
    • Emits a lazy-loading ESM index (index.js) using getter functions + createRequire.
    • Emits rule_names.ts consumed by the TSDown config.
  • apps/oxlint/scripts/build.ts — Runs the codegen script before TSDown and deletes src-js/generated/plugin-eslint/ afterwards.
  • apps/oxlint/tsdown.config.ts — Three new build configs: ESM main entry (+ DTS), CJS main entry, and all 292 CJS rule files output to dist-pkg-plugin-eslint/.
  • oxc_release.toml — Adds npm/oxlint-plugin-eslint/package.json to the oxlint versioned-files list.
  • .github/workflows/release_apps.yml — Copies dist-pkg-plugin-eslint/ into the npm package and publishes it alongside @oxlint/plugins.

ESLint 9 exports restriction

ESLint 9 blocks access to eslint/lib/rules/* via its exports map. Each generated rule wrapper bypasses this using:

const { createRequire } = require("node:module");
// createRequire resolves relative paths from eslint's package root, bypassing its exports map.
const _require = createRequire(require.resolve("eslint/package.json"));
module.exports = _require("./lib/rules/no-unused-vars.js");

Rules are lazy-loaded via getter functions — a rule's code is only require()d when the rule is first accessed.

Original prompt

This section details on the original issue you should resolve

<issue_title>linter: introduce oxlint-plugin-eslint</issue_title>
<issue_description></issue_description>

<agent_instructions>Follow the instructions in my (@overlookmotel) comment on the issue.</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@overlookmotel We need a package which exports ESLint's built-in rules as a JS plugin which Oxlint users can use.

I propose:

Package

  • Package name: oxlint-plugin-eslint.
  • meta.name defined in package: eslint-js
  • Usage in config:
{
  "jsPlugins": ["oxlint-plugin-eslint"],
  "rules": {
    "eslint-js/unsupported-rule": "error"
  }
}

It's a bit weird that package name and meta.name don't match, but this is a special case, and this is the simplest for users. All other options I can think of are ugly or problematic in one way or another.

How

Package & directories

  1. Add a package directory npm/oxlint-plugin-eslint (modelled on oxlint-plugins).
  2. Add directory apps/oxlint/src-js/plugin-eslint to contain the implementation.

Implementation will be built with TSDown and the dist files copied into npm/oxlint-plugin-eslint.

Implementation

Design goals

  1. All ESLint's rules are included in the package (including the ones which Oxlint already implements natively).
  2. Code for rule is lazy-loaded only when that rule is used.
  3. Avoid including all the rest of ESLint's code in this package. Just the rules themselves.

Reason for including all rules is that users can switch to the JS plugin version of a rule as a "hotfix" if they run into a bug in Oxlint's native version of the rule.

Entry point

apps/oxlint/src-js/plugin-eslint will contain only 1 file:

// apps/oxlint/src-js/plugin-eslint/index.js
import rules from "../generated/plugin-eslint/index.js";

export default {
  meta: {
    name: "eslint-js",
  },
  rules,
};

The whole rest of implementation will be generated.

Build script

Create a script in apps/oxlint/scripts which:

  • Gets list of all files in ESLint's lib/rules directory (eslint is a dev dependency of apps/oxlint).
  • For each rule, generates a file in apps/oxlint/src-js/generated/plugin-eslint/rules like:
// apps/oxlint/src-js/generated/plugin-eslint/rules/accessor-pairs.cjs
module.exports = require("eslint/lib/rules/accessor-pairs.js");
  • Generates entry point of the plugin:
// apps/oxlint/src-js/generated/plugin-eslint/index.js
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);

export default {
  get "accessor-pairs"() { return require("./rules/accessor-pairs.cjs") },
  get "array-bracket-newline"() { return require("./rules/array-bracket-newline.cjs") },
  // ... etc for all rules ...
};
  • Generates files list for inclusion in TSDown build config:
// apps/oxlint/src-js/generated/plugin-eslint/rule_names.ts
export default [
  "accessor-pairs",
  "array-bracket-newline",
  // ... etc for all rules ...
];

Main build script

Make scripts/build.ts run the sub-build script before running TSDown.

After running TSDown, it should delete the generated/plugin-eslint directory again.

TSDown config

Add to TSDown config a separate config which builds the plugin-eslint directory.

It should use the list of rule names imported from generated rule_names.ts to construct entry property of this config.

The build should be generated in dist-pkg-plugin-eslint directory.

Release workflow

Add to release workflow that it copies contents of dist-pkg-plugin-eslint directory into npm/oxlint-plugin-eslint and bumps the version etc. Copy the workflow for npm/oxlint-plugin.</comment_new>
<comment_new>@overlookmotel
@copilot Get to it!

Probably some of the details in my implementation above aren't quite right, in particular making sure the require calls in plugin-eslint/index.js refer to the right place in the built package. You may have to tweak some things.</comment_new>


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI changed the title [WIP] Add oxlint-plugin-eslint package for ESLint rules linter: introduce oxlint-plugin-eslint Feb 26, 2026
@github-actions github-actions bot added A-linter Area - Linter A-cli Area - CLI A-linter-plugins Area - Linter JS plugins labels Mar 3, 2026
Copilot AI and others added 4 commits March 4, 2026 15:05
Co-Authored-By: overlookmotel <557937+overlookmotel@users.noreply.github.com>
@overlookmotel overlookmotel force-pushed the copilot/introduce-oxlint-plugin-eslint branch from b021b07 to 4d9c1a2 Compare March 4, 2026 15:05
@overlookmotel
Copy link
Member

Closing in favour of #20009, which was based on the work in this PR.

@overlookmotel overlookmotel deleted the copilot/introduce-oxlint-plugin-eslint branch March 6, 2026 01:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-cli Area - CLI A-linter Area - Linter A-linter-plugins Area - Linter JS plugins

Projects

None yet

Development

Successfully merging this pull request may close these issues.

linter: introduce oxlint-plugin-eslint

2 participants