npm.io
0.1.3 • Published 2d ago

@dobroslavlabs/oxlint

Licence
MIT
Version
0.1.3
Deps
1
Size
503 kB
Vulns
0
Weekly
0

@dobroslavlabs/oxlint

Custom Oxlint JS plugin + Ultracite-style presets for JS/TS, React/JSX, Effect, Elysia, Bun, and Zod.

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 / __filenameimport.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:sqlitebun:sqlite
prefer-bun-sqlite-strict Prefer new Database(…, { strict: true })
prefer-bun-shell execa / child_process.exec* shell strings →

@dobroslavlabs/oxlint

Custom Oxlint JS plugin + Ultracite-style presets for JS/TS, React/JSX, Effect, Elysia, Bun, and Zod.

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 / __filenameimport.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:sqlitebun:sqlite
prefer-bun-sqlite-strict Prefer new Database(…, { strict: true })
prefer-bun-shell execa / child_process.exec* shell strings → …`
prefer-response-json JSON.stringify + end/send/ResponseResponse.json
no-bun-sleep-sync-in-async No Bun.sleepSync inside async — use await Bun.sleep
import bun from "@dobroslavlabs/oxlint/bun";

export default defineConfig({
  extends: [recommended, bun],
});

Develop

bun install
bun run test        # Vitest + Oxlint RuleTester (Node 22+)
bun run typecheck   # strict TypeScript 7
bun run fmt         # oxfmt
bun run lint        # build + ultracite check (dogfoods this package)
bun run build       # tsdown → dist (publint + attw + unused)
bun run check       # build → typecheck → test → fmt:check → lint

Stack: Bun · TypeScript 7 · Ultracite (oxlint + oxfmt) · tsdown · Vitest · @oxlint/plugins

CI (.github/workflows/ci.yml) runs check on main push/PR. It does not publish.

Editor: Oxc VS Code extension (format + fix on save — import sort via oxfmt, unused-import removal via oxlint).

Publish (local only)

bun run login      # npm login --auth-type=web (browser / 2FA)
bun run pack:dry   # check + npm pack --dry-run
bun run release    # npm publish --access public

prepublishOnly runs check; prepack runs build. See CHANGELOG.md for release notes.

Keywords