Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/mpsuesser/effect-oxlint/llms.txt

Use this file to discover all available pages before exploring further.

An oxlint plugin is a plain object with a meta.name string and a rules record mapping rule names to CreateRule instances. effect-oxlint exports a Plugin module with two functions — Plugin.define and Plugin.merge — that make it straightforward to assemble rules into a plugin and export it from a module that oxlint can load.

What an oxlint plugin is

At runtime, oxlint expects a plugin to match the OxlintPlugin shape:
type OxlintPlugin = {
  meta: { name: string };
  rules: Record<string, CreateRule>;
};
Plugin.define constructs this object from a name and a map of rule names to CreateRule values. Because Rule.define (and all the convenience factories like Rule.banMember) return CreateRule, you can pass them directly without any additional wrapping.

Plugin.define

Pass a name and a rules record to Plugin.define. Rule names should use kebab-case — oxlint references them as plugin-name/rule-name in diagnostics and configuration.
import { Plugin, Rule } from 'effect-oxlint';

const noJsonParse = Rule.banMember('JSON', ['parse', 'stringify'], {
  message: 'Use Schema for JSON decoding instead of JSON.parse'
});

const noMathRandom = Rule.banMember('Math', 'random', {
  message: 'Use the Effect Random service instead'
});

const noNodeFs = Rule.banImport('node:fs', {
  message: 'Use the Effect FileSystem service instead'
});

const noThrow = Rule.banStatement('ThrowStatement', {
  message: 'Use Effect.fail instead of throw'
});

export default Plugin.define({
  name: 'my-effect-rules',
  rules: {
    'no-json-parse': noJsonParse,
    'no-math-random': noMathRandom,
    'no-node-fs': noNodeFs,
    'no-throw': noThrow
  }
});
The exported default is the value oxlint reads when it loads the plugin module.

Naming conventions

1

Plugin name

Use kebab-case for the plugin name (e.g. my-effect-rules). oxlint prefixes every rule with this name in diagnostics: my-effect-rules/no-json-parse.
2

Rule keys in the rules record

Use kebab-case for rule keys (e.g. no-json-parse). The key you choose here is what users write in their oxlint configuration, not the name field on the RuleConfig passed to Rule.define.
3

Auto-generated names from convenience factories

Convenience factories (Rule.banMember, Rule.banCallOf, etc.) auto-generate an internal rule name from their arguments. For example, Rule.banMember('JSON', ['parse', 'stringify'], ...) generates the internal name ban-json-parse-stringify. This name appears in tracing spans but is not the name exposed to oxlint — that comes from the key you assign in the rules record.

Plugin.merge

Plugin.merge combines multiple OxlintPlugin objects into one. This is useful for splitting a large rule set across several files and joining them at the export boundary, or for re-exporting third-party plugins alongside your own.
import { Plugin } from 'effect-oxlint';
import { corePlugin } from './plugins/core.ts';
import { asyncPlugin } from './plugins/async.ts';

export default Plugin.merge(corePlugin, asyncPlugin);
If two plugins define a rule with the same name, the later plugin’s definition wins. The merged plugin’s meta.name is the joined names of all input plugins separated by +.
For projects with more than a handful of rules, split the rule definitions and plugin assembly across separate files:
import { Rule } from 'effect-oxlint';

export const noJsonParse = Rule.define({
  name: 'no-json-parse',
  meta: Rule.meta({
    type: 'suggestion',
    description: 'Use Schema for JSON decoding instead of JSON.parse'
  }),
  create: function* () {
    // ...
  }
});
This structure keeps individual rule files focused on a single concern and makes the plugin.ts file a clear manifest of everything the plugin exposes.

How oxlint loads plugins

oxlint resolves plugins through its configuration file. Reference the plugin’s module path under the plugins key, then enable individual rules under rules:
{
  "plugins": ["./plugin.ts"],
  "rules": {
    "my-effect-rules/no-json-parse": "error",
    "my-effect-rules/no-math-random": "warn"
  }
}
The default export of plugin.ts is the OxlintPlugin object produced by Plugin.define. oxlint reads meta.name from it to namespace the rules.
For Bun and Deno projects, oxlint can import TypeScript plugin files directly. For Node.js projects, ensure the plugin file is processed by a TypeScript-aware bundler or runner (such as tsx) before oxlint attempts to load it.

Using Plugin.merge for large rule sets

Plugin.merge is especially helpful when you want to organise rules into domain-specific sub-plugins and compose them at the top level:
import { Plugin } from 'effect-oxlint';
import { noJsonParse, noMathRandom } from './rules/data.ts';
import { noThrow, noRunSync } from './rules/effects.ts';

const dataPlugin = Plugin.define({
  name: 'my-effect-rules-data',
  rules: { 'no-json-parse': noJsonParse, 'no-math-random': noMathRandom }
});

const effectsPlugin = Plugin.define({
  name: 'my-effect-rules-effects',
  rules: { 'no-throw': noThrow, 'no-run-sync': noRunSync }
});

// Export a single combined plugin
export default Plugin.merge(dataPlugin, effectsPlugin);

Effect patterns

The full set of Effect idioms used in effect-oxlint rules.

Handler error channel

Why handlers must have error channel never and how to handle fallible sub-effects.

Build docs developers (and LLMs) love