oxlint-config-vibeproof
An unofficial port of SukkaW's lint preset (eslint-config-sukka 9.1.0 +
eslint-plugin-vibe-proof 1.10.0) to
oxlint and oxfmt.
Not published or endorsed by Sukka; report problems here, not upstream.
- Most rules are oxlint's native (Rust) rules, including type-aware ones through
oxlint-tsgolint. - Rules oxlint doesn't have run as the original ESLint plugins through oxlint's JS plugin support. They are checked to report exactly what ESLint reports.
- Formatting is done by oxfmt instead of
@stylistic.
Install
pnpm add -D oxlint @caxerx/oxlint-config-vibeproof
pnpm add -D oxlint-tsgolint # optional: type-aware rules (recommended for TypeScript projects)
pnpm add -D oxfmt # optional: formatting
Requires Node.js ≥ 22.18 (oxlint loads .ts config files with Node's type stripping).
Usage
// oxlint.config.ts
import { defineConfig } from 'oxlint';
import { sukka } from '@caxerx/oxlint-config-vibeproof';
export default defineConfig({
extends: [sukka()],
rules: {
// your overrides
}
});
// oxfmt.config.ts
import { defineConfig } from 'oxfmt';
import { oxfmt } from '@caxerx/oxlint-config-vibeproof/oxfmt';
export default defineConfig(oxfmt());
npx oxlint # type-aware rules run automatically when oxlint-tsgolint is installed
npx oxfmt
oxlint only discovers oxlint.config.ts (or .oxlintrc.json) automatically. Other file names need -c.
Options
Same options and auto-detection as eslint-config-sukka's sukka(). Each preset accepts true, false, or an options object; when left out it is enabled based on what your project has installed.
| Option | Default | Notes |
|---|---|---|
js | true | { disableNoConsoleInCLI, env: { browser, webextensions, greasemonkey, customGlobals } }. module is read from package.json "type". |
ts | if typescript or @types/node is installed | { allowJs, componentExtensions, typeAware }. typeAware defaults to whether oxlint-tsgolint is installed. |
react | if react, @types/react or next is installed (needs ts) | { files, additionalHooks, additionalHooksWithType, additionalComponents, nextjs, remix, reactRefresh: { allowConstantExport } } |
next | if next is installed | |
stylex | if @stylexjs/stylex or stylex-webpack is installed | { opt }, passed to @stylexjs/eslint-plugin |
node | if @types/node or @types/bun is installed | { strict, module, files } |
legacy | false | { browser, node, files } |
ignores | built-in globs | { customGlobs }. .gitignore is always honoured by oxlint. |
stylistic | true | The few style rules oxfmt can't enforce |
cwd | process.cwd() | Where to look for installed packages |
oxfmt({ printWidth }): printWidth defaults to 120.
Migrating from eslint-config-sukka
A step-by-step guide for a project that uses eslint-config-sukka (with or without eslint-plugin-vibe-proof).
Options are above; what is not covered is below.
1. Swap the dependencies
pnpm remove eslint eslint-config-sukka eslint-plugin-vibe-proof # plus any @eslint-sukka/* packages you added
pnpm add -D oxlint @caxerx/oxlint-config-vibeproof
pnpm add -D oxlint-tsgolint # type-aware rules; recommended for TypeScript projects
pnpm add -D oxfmt # formatting, replaces @stylistic
Node.js ≥ 22.18 is required.
2. Replace the config
// before: eslint.config.js
import { sukka } from 'eslint-config-sukka';
export default sukka({ react: { nextjs: true } }, {
rules: { 'no-console': 'off' }
});
// after: oxlint.config.ts
import { defineConfig } from 'oxlint';
import { sukka } from '@caxerx/oxlint-config-vibeproof';
export default defineConfig({
extends: [sukka({ react: { nextjs: true } })],
rules: { 'no-console': 'off' }
});
// oxfmt.config.ts
import { defineConfig } from 'oxfmt';
import { oxfmt } from '@caxerx/oxlint-config-vibeproof/oxfmt';
export default defineConfig(oxfmt());
sukka()takes the same options and auto-detects the same presets.- Extra flat-config objects you passed after the options become oxlint's
rulesandoverrides. An object withfilesbecomesoverrides: [{ files: [...], rules: {...} }]. - Rule ids mostly stay the same. oxlint matches
@typescript-eslint/x,import-x/x,unicorn/xand similar to its native rules by rule name. The 66 that now run under another name are listed indata/directive-compat.json, for examplecamelcase→sukka-compat/camelcase. - Delete
eslint.config.js. oxlint only discoversoxlint.config.ts(or.oxlintrc.json) automatically; other names need-c. .gitignoreis always honoured, so mostignoresentries can go.
3. Rewrite disable comments
oxlint understands eslint-disable* comments, so most keep working. Comments naming a rule that runs under a new name need rewriting:
npx oxlint-config-vibeproof-migrate --dry-run # preview
npx oxlint-config-vibeproof-migrate # rewrite in place
Comments naming a rule with no oxlint version, such as the jsonc/* and yml/* rules or @stylistic/* rules replaced by oxfmt,
are reported as unused. Delete them by hand; the codemod counts them for you.
4. Update scripts, CI and editor
// package.json
"scripts": {
"lint": "oxlint", // was: eslint .
"lint:fix": "oxlint --fix", // was: eslint . --fix
"format": "oxfmt",
"format:check": "oxfmt --check"
}
- CI: run
oxlint --deny-warningsandoxfmt --check. Addnpx oxlint-config-vibeproof-migrate --checkto fail when a disable comment needs migrating. - lint-staged / pre-commit hooks: swap
eslint --fixforoxlint --fixandoxfmt. - VS Code: replace the ESLint extension with the oxc extension, which runs both oxlint and oxfmt.
5. Format once and review
Run npx oxfmt and commit the result separately so the reformat is easy to skip in git blame
(add the commit to .git-blame-ignore-revs). The visible style changes are interface and type members ending with ;,
function* g() instead of function *g(), and a few line-break choices; see docs/stylistic-oxfmt.md.
Then run npx oxlint. Expect small differences from ESLint; each known one is listed with its reason in
packages/harness/allowlist.json.
Upgrading from 0.1
- The package moved from GitHub Packages to npm. Remove the
@caxerx:registry=https://npm.pkg.github.comline (and its token) from.npmrcand reinstall. react-hooks/configandreact-hooks/gatingare no longer run, andeslint-plugin-react-hooksis no longer a dependency. Neither rule can report anything without options this preset never sets. Remove anyreact-hooks-js/*entries from your config and disable comments.
What is not covered
| Why | |
|---|---|
JSON, YAML, package.json and pnpm-workspace.yaml rules (jsonc/*, yml/*, pnpm/*) | oxlint only lints JS/TS. oxfmt still formats JSON and YAML. |
66 @stylistic formatting rules | Replaced by oxfmt |
8 rules that need type information inside a JS plugin, e.g. vibe-proof/no-indexof-equality and @eslint-react/no-leaked-conditional-rendering | oxlint gives JS plugins no type information. Tracked in docs/blocked-rules.md and probed weekly in CI. |
import-x/no-unresolved, no-relative-packages, no-useless-path-segments | The TypeScript resolver can't be configured from oxlint's JSON settings. tsc already reports unresolved imports. |
n/process-exit-as-throw | It patches ESLint's code-path analysis, which oxlint doesn't use |
Known differences from ESLint, each one a documented oxlint behaviour or bug, are listed in
packages/harness/allowlist.json. Examples:
- Imports:
no-unused-varsalso reports unused imports in TS files. - React:
react/only-export-componentsmisses components wrapped inReact.memo(...). - vibe-proof: 7 rules run without their optional type refinement, so a few non-array receivers may be reported.
How the port is verified
packages/harness runs eslint-config-sukka and this preset on the same corpus and compares their reports rule by rule and line by line. It also:
- replays
eslint-plugin-vibe-proof's own test suite through oxlint; - checks that oxfmt and the kept
@stylisticrules don't fight; - detects drift when
eslint-config-sukkachanges; - tests which old disable-comment ids still match.
Credits
The rule selection, rule options, and the ESLint plugins this preset runs are the work of Sukka (SukkaW):
eslint-config-sukka, eslint-plugin-vibe-proof
and eslint-plugin-sukka. This package only adapts them to oxlint.
License
MIT. Rule configuration ported from MIT-licensed eslint-config-sukka and eslint-plugin-vibe-proof; see THIRD-PARTY-NOTICES.md.