@dobroslavlabs/oxlint
Custom Oxlint JS plugin + Ultracite-style presets for JS/TS, React/JSX, Effect, Elysia, Bun, and Zod.
- Package:
@dobroslavlabs/oxlint(0.1.3) - Repo: github.com/dobroslavlabs/oxlint
- Author: Dobroslav Radosavljevic
- Rule IDs:
dobroslavlabs/<rule> - Changelog: CHANGELOG.md
Quick start
bun add -d oxlint @dobroslavlabs/oxlint
// oxlint.config.ts
import { defineConfig } from "oxlint";
import recommended from "@dobroslavlabs/oxlint/recommended";
export default defineConfig({
extends: [recommended],
});
Or pick surfaces:
import { defineConfig } from "oxlint";
import core from "@dobroslavlabs/oxlint/core"; // JS/TS
import react from "@dobroslavlabs/oxlint/react"; // JSX/TSX + hooks
import effect from "@dobroslavlabs/oxlint/effect"; // Effect (opt-in)
import elysia from "@dobroslavlabs/oxlint/elysia"; // Elysia (opt-in)
import bun from "@dobroslavlabs/oxlint/bun"; // Bun (opt-in)
import zod from "@dobroslavlabs/oxlint/zod"; // Zod (opt-in)
export default defineConfig({
extends: [core, react, effect, elysia, bun, zod],
});
Presets
| Import | Scope | Enables |
|---|---|---|
@dobroslavlabs/oxlint/recommended |
core + react | Default JS/TS + React set |
@dobroslavlabs/oxlint/core |
all files | Plain JS/TS rules |
@dobroslavlabs/oxlint/react |
React/JSX (+ hooks) | Component / JSX / hook rules |
@dobroslavlabs/oxlint/effect |
all files | Effect / tagged-error rules (opt-in) |
@dobroslavlabs/oxlint/elysia |
all files | ElysiaJS best-practice rules (opt-in) |
@dobroslavlabs/oxlint/bun |
all files | Prefer Bun-native APIs (opt-in) |
@dobroslavlabs/oxlint/zod |
all files | Zod schema rules (opt-in) |
@dobroslavlabs/oxlint |
— | Plugin only; enable dobroslavlabs/* yourself |
Each preset sets jsPlugins: ["@dobroslavlabs/oxlint"] and turns on the matching rules. /effect, /elysia, /bun, and /zod are not included in /recommended.
Rules
Core
| Rule | Summary |
|---|---|
require-jsdoc |
Descriptive JSDoc on complex functions (defaults: statements ≥4, params ≥3, complexity ≥3) |
no-double-type-assertion |
Ban nested / as unknown as assertions |
no-response-json-type-assertion |
Ban asserting any *.json() call output |
no-dom-target-type-assertion |
Ban casting event.target / e.target to DOM elements |
no-direct-process-env |
Ban process.env (allows NODE_ENV, VITEST, JEST_WORKER_ID by default) |
no-index-barrels |
Ban index.* barrel files (allow for package entrypoints) |
no-index-imports |
Ban imports with an explicit /index path segment |
no-pure-re-export-files |
Ban non-index files that only re-export |
no-forbidden-imports |
Configurable import denylist (paths / patterns) — no-op until configured |
Most core rules accept { allow?: string[] } (path substrings — use distinctive fragments). no-direct-process-env also takes allowKeys ([] bans all keys; does not fall back to defaults).
Notes: no-index-imports only flags explicit /index paths (directory imports like ./components are allowed).
"dobroslavlabs/require-jsdoc": ["error", { minStatements: 5, minParams: 4 }],
"dobroslavlabs/no-index-barrels": ["error", { allow: ["src/index.ts"] }],
"dobroslavlabs/no-direct-process-env": ["error", { allowKeys: ["NODE_ENV", "CI"] }],
"dobroslavlabs/no-forbidden-imports": [
"error",
{
paths: [{ name: "lucide-react", message: "Use Hugeicons instead." }],
patterns: [{ regex: "^@unpic/react", message: "Use the shared Image component." }],
},
],
React
| Rule | Summary |
|---|---|
no-native-html |
Ban high-confidence native HTML; suggest UI components by name |
no-react-namespace |
Ban import React / import * as React and React.* access |
component-file-name-match |
First primary exported component matches kebab file basename |
no-multi-component-files |
One module-level primary component per file (*Impl / *Provider / *Context ignored) |
hook-file-name-match |
Hook in use-*.ts(x) matches basename |
no-multi-hook-files |
One hook per use-*.ts(x) file |
no-jsx-module-constants |
No module-level JSX const |
no-jsx-local-constants-in-components |
No local JSX const inside components |
no-jsx-variable-reassignment-in-components |
No JSX via local reassignment |
no-jsx-iife-in-components |
No JSX-returning IIFEs in components |
no-render-helper-functions-in-components |
No nested JSX-returning helpers in components |
React JSX rules are scoped to **/*.{jsx,tsx} in the react preset. Hook rules and no-react-namespace also apply to plain .ts (React types / hooks). Most accept { allow?: string[] }.
Limits (v1): class components and forwardRef wrappers are not covered by file-shape / JSX-hygiene rules. Secondary named exports after the first primary component are ignored by component-file-name-match.
no-native-html default ban list:button · a · input · textarea · select · option · optgroup · label · img · video · audio · picture · source · iframe · dialog · details · summary · progress · ul · ol · li · table · thead · tbody · tfoot · tr · th · td · caption · hr
"dobroslavlabs/no-native-html": ["error", { tags: ["button", "a"] }],
Effect (opt-in — @dobroslavlabs/oxlint/effect)
| Rule | Summary |
|---|---|
no-run-promise-in-domain |
Ban runPromise* outside boundary paths (defaults: /routes/, /runtime/, /cli/, /main.ts, …) |
no-run-promise-in-effect-tests |
Ban runPromise* in tests — prefer @effect/vitest it.effect |
no-run-effect-inside-effect |
Ban runners inside Effect.gen / Effect.fn |
no-try-catch-in-effect-gen |
Ban try/catch inside gen/fn — use Effect.try / catchTag |
prefer-return-yield-star |
Prefer return yield* for the terminal yield in a gen/fn body |
no-outdated-effect-api |
Ban curated Effect v3 APIs (Effect.catchAll, Context.Tag, …) |
no-effect-run-sync-at-module-scope |
Ban top-level runSync / Effect.runSync / *.runSync |
no-singleton-service-export |
Ban export const fooService = new FooService() |
require-tagged-error-class |
Prefer Schema.TaggedErrorClass over extends Error / Schema.TaggedError |
require-tagged-error-message |
Require message on TaggedErrorClass()(tag, schema) schemas |
no-effect-wrapper-helpers |
Ban unwrap / fromResult bridges (narrow filenames: from-nullable, effect-result, …) |
no-multiple-effect-provide |
Prefer one composed Layer over many Effect.provide in a pipe chain |
prefer-effect-fn |
Prefer Effect.fn("Name") over bare module-scope Effect.gen (skips program/main/boundaries) |
no-tagged-error-instanceof |
Ban instanceof SomeError for tagged/domain errors (use catchTag) |
no-generic-error-in-fail |
Ban new Error(...) in Effect.fail / Result.err (configurable methods) |
Most accept { allow?: string[] }. Pair with @effect/language-service for type-aware checks (floating Effect, full outdated API catalog, etc.).
import effect from "@dobroslavlabs/oxlint/effect";
export default defineConfig({
extends: [recommended, effect],
});
Zod (opt-in — @dobroslavlabs/oxlint/zod)
| Rule | Summary |
|---|---|
zod-modern-format-validators |
Prefer z.email() / z.url() / z.uuid() over z.string().email() style |
zod-schema-naming |
Exported z.* schema defs must be PascalCase *Schema |
zod-schema-naming flags definitional z.object() / z.string() etc., not UserSchema.pick() or makeUserSchema().
import zod from "@dobroslavlabs/oxlint/zod";
export default defineConfig({
extends: [recommended, zod],
});
Elysia (opt-in — @dobroslavlabs/oxlint/elysia)
Rules only run in files that import from elysia.
| Rule | Summary |
|---|---|
no-context-param |
Ban typing handler params as Context — destructure fields |
prefer-status-helper |
Prefer status(code, value) over set.status in handlers/hooks |
require-route-schema |
Require body/query/params/… on post/put/patch (honors .guard() / .group()) |
require-plugin-name |
Require { name } on exported new Elysia() plugins (skips .listen / entry/modules paths) |
no-functional-plugin-callback |
Prefer new Elysia() over .use((app) => …) |
no-controller-context-class |
Ban class methods typed with Context |
prefer-throw-status |
Prefer status() / throw status() over throw new Error / string throws |
no-cookie-undefined-check |
Check cookie.x.value, not cookie.x as optional |
require-response-schema |
When handler uses bare status(), declare a response schema |
import elysia from "@dobroslavlabs/oxlint/elysia";
export default defineConfig({
extends: [recommended, elysia],
});
Bun (opt-in — @dobroslavlabs/oxlint/bun)
For Bun apps. Prefer Bun-native APIs over Node fallbacks (docs: Bun.file / Bun.write / Bun.spawn / bun:sqlite). Does not ban node:fs directory APIs (readdir, mkdir, …). Use { allow } for isomorphic packages.
| Rule | Summary | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
prefer-bun-file-read |
fs.readFile* → Bun.file(…).text() / .json() / .bytes() |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
prefer-bun-write |
fs.writeFile* → Bun.write (not appendFile) |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
prefer-bun-spawn |
child_process.spawn/fork/execFile* → Bun.spawn / Bun.spawnSync |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
prefer-import-meta-paths |
__dirname / __filename → import.meta.dir / import.meta.path |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
no-node-http-server |
Ban http(s).createServer — use Bun.serve or Elysia/Hono |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
prefer-bun-sqlite |
Ban better-sqlite3 / sqlite3 / node:sqlite → bun:sqlite |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
prefer-bun-sqlite-strict |
Prefer new Database(…, { strict: true }) |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
prefer-bun-shell |
execa / child_process.exec* shell strings → |