oxlint-compat-wrapper
v1.0.0
Published
Run any ESLint plugin in OxLint. Patches context.getScope, getAncestors, getDeclaredVariables and adds eslint-disable comment support for JS plugins.
Maintainers
Readme
oxlint-compat-wrapper
Run any ESLint plugin in OxLint. One function, one line, zero config.
const wrap = require('oxlint-compat-wrapper');
module.exports = wrap(require('eslint-plugin-sonarjs'));The problem
OxLint's JS plugin API is ESLint-compatible but not ESLint-identical. Three things break when you load an existing ESLint plugin:
context.getScope() → "not a function"
context.getAncestors() → "not a function"
context.getDeclaredVariables() → "not a function"
eslint-disable comments → silently ignoredThese methods exist in OxLint — but on sourceCode, not context (ESLint moved them in v9). Every ESLint plugin written before 2025 uses the old API. Without this wrapper, they crash.
The eslint-disable gap is a known open bug in OxLint with no fix planned.
What this wrapper does
For every rule in the wrapped plugin:
context.getScope()→ proxied tosourceCode.getScope(currentNode)context.getAncestors()→ proxied tosourceCode.getAncestors(currentNode)context.getDeclaredVariables()→ proxied tosourceCode.getDeclaredVariables(currentNode)context.markVariableAsUsed()→ proxied tosourceCode.markVariableAsUsed()context.report()→ intercepted to checkeslint-disablecomments before reporting- Frozen context → handled by copying properties to a new object (OxLint freezes the context)
eslint-disable support
Parses and honors all ESLint disable directive formats:
// eslint-disable-next-line no-console
console.log('suppressed');
console.log('suppressed'); // eslint-disable-line no-console
/* eslint-disable no-console */
console.log('suppressed');
console.log('also suppressed');
/* eslint-enable no-console */
/* eslint-disable */
// everything in this file suppressedSupports rule-specific disabling (eslint-disable-next-line rule-a, rule-b) and blanket disabling.
Installation
npm install -D oxlint-compat-wrapper
# or
pnpm add -D oxlint-compat-wrapperUsage
Create a thin wrapper file for any ESLint plugin you want to use in OxLint:
// my-sonarjs-compat.js
const wrap = require('oxlint-compat-wrapper');
module.exports = wrap(require('eslint-plugin-sonarjs'));Then reference it in .oxlintrc.json:
{
"jsPlugins": [
{ "name": "sonarjs", "specifier": "./my-sonarjs-compat.js" }
],
"rules": {
"sonarjs/cognitive-complexity": "warn",
"sonarjs/no-duplicate-string": "warn"
}
}That's it. Every rule in the plugin now works in OxLint with full eslint-disable support.
Tested plugins
| ESLint Plugin | Rules | Status |
|--------------|:-----:|:------:|
| eslint-plugin-sonarjs | 29 | Working |
| eslint-plugin-import | 10 | Working |
| eslint-plugin-jsx-a11y | 5 | Working |
| eslint-plugin-lodash | 1 | Working |
| ESLint core rules (via Linter.getRules()) | 10 | Working |
| Custom ESLint plugins | 20+ | Working |
Cherry-picking ESLint core rules
You can extract specific built-in ESLint rules that OxLint doesn't have natively:
// my-eslint-core.js
const wrap = require('oxlint-compat-wrapper');
const { Linter } = require('eslint');
const linter = new Linter();
const allRules = linter.getRules();
const picked = {};
for (const name of ['camelcase', 'no-return-await', 'prefer-arrow-callback']) {
const rule = allRules.get(name);
if (rule) picked[name] = rule;
}
module.exports = wrap({ rules: picked });{
"jsPlugins": [
{ "name": "eslint-core", "specifier": "./my-eslint-core.js" }
],
"rules": {
"eslint-core/camelcase": "warn",
"eslint-core/no-return-await": "warn"
}
}How it works
OxLint's JS plugin API provides sourceCode.getScope(), sourceCode.getAncestors(), etc. — but legacy ESLint plugins expect them on context. The wrapper:
- Copies all properties from the frozen
contextto a new plain object - Adds the missing methods by proxying to
sourceCode - Tracks the
currentNodeas the visitor traverses the AST — sogetScope()returns the scope for the node being visited, not the root - Wraps
context.report()to parse and honoreslint-disablecomments before forwarding
The eslint-disable parser runs once per file (when the rule is first invoked), builds a map of disabled lines/ranges, and checks each report() call against it. Overhead is ~1-2ms per file.
Why not wait for OxLint to fix this?
sourceCode.getScope()was added in OxLint v1.45 (Oct 2025) — butcontext.getScope()is not planned. OxLint considers it deprecated.eslint-disablefor JS plugins is an open bug (#20747) since March 2026 with no assignee, no milestone, no fix timeline.- Hundreds of ESLint plugins use the legacy API. Waiting means they can't be used in OxLint.
This wrapper bridges the gap today.
API
const wrap = require('oxlint-compat-wrapper');
const wrappedPlugin = wrap(eslintPlugin);Parameters:
eslintPlugin— an ESLint plugin object with arulesproperty ({ rules: { 'rule-name': ruleObject } })
Returns:
- A new plugin object with the same rules, wrapped for OxLint compatibility
The wrapper does not modify the original plugin.
License
MIT
