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.

This guide walks you through writing a custom oxlint rule with effect-oxlint from scratch. You will define a rule using Rule.define, explore the convenience factories for common patterns, assemble your rules into a plugin, and verify them with the built-in testing utilities.
1

Install effect-oxlint

Install effect-oxlint and its peer dependency effect:
npm install effect-oxlint effect@4.0.0-beta.57
effect-oxlint ships as TypeScript source with no compiled dist/. It works out of the box with Bun and Deno. For Node.js, use a bundler or tsx. See Installation for full runtime details.
2

Define a rule with Rule.define

Rule.define is the primary entry point. Write create as an Effect generator: use yield* to access RuleContext, create Ref-based state, and return a visitor map where each handler returns an Effect<void>.The example below bans JSON.parse and JSON.stringify, directing developers to use Schema for JSON decoding instead:
no-json-parse.ts
import * as Effect from 'effect/Effect';
import * as Option from 'effect/Option';
import { AST, Diagnostic, Rule, RuleContext } from 'effect-oxlint';

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* () {
    const ctx = yield* RuleContext;
    return {
      // node is typed as ESTree.MemberExpression automatically
      MemberExpression: (node) =>
        Option.match(
          AST.matchMember(node, 'JSON', ['parse', 'stringify']),
          {
            onNone: () => Effect.void,
            onSome: (matched) =>
              ctx.report(
                Diagnostic.make({
                  node: matched,
                  message: 'Use Schema for JSON'
                })
              )
          }
        )
    };
  }
});
AST.matchMember returns Option<ESTree.MemberExpression>. When it returns Some, the matched node is narrowed and passed directly to ctx.report. When it returns None, the handler returns Effect.void — no diagnostic, no side effects.
3

Use convenience factories for common patterns

For the most common ban patterns, effect-oxlint provides factory functions that generate a full CreateRule from a single call. These cover member expressions, imports, statements, bare identifier calls, new expressions, method calls, and combinations of multiple patterns.
rules.ts
import { Rule } from 'effect-oxlint';

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

// Ban an import by source path
const noNodeFs = Rule.banImport('node:fs', {
  message: 'Use the Effect FileSystem service instead'
});

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

// Ban bare identifier calls: fetch()
const noFetch = Rule.banCallOf('fetch', {
  message: 'Use Effect HTTP client instead'
});

// Ban new expressions: new Date()
const noNewDate = Rule.banNewExpr('Date', {
  message: 'Use Clock service instead'
});

// Ban obj.prop(...) method calls: Effect.runSync, Effect.runPromise
const noRunSync = Rule.banCallOfMember('Effect', ['runSync', 'runPromise'], {
  message: 'Keep effects composable — run only at the entry point'
});

// Ban multiple statement types under one rule
const noImperativeLoops = Rule.banMultiple(
  {
    statements: [
      'ForStatement',
      'ForInStatement',
      'ForOfStatement',
      'WhileStatement',
      'DoWhileStatement'
    ]
  },
  { message: 'Use Arr.map / Effect.forEach instead' }
);

// Combine different ban types in a single rule
const useClockService = Rule.banMultiple(
  {
    newExprs: 'Date',
    members: [['Date', 'now']]
  },
  { message: 'Use Clock service' }
);
4

Assemble rules into a plugin

Use Plugin.define to assemble your rules into an oxlint-compatible plugin. The object maps rule names (as they will appear in oxlint config) to CreateRule instances.
plugin.ts
import { Plugin } from 'effect-oxlint';

export default Plugin.define({
  name: 'my-effect-rules',
  rules: {
    'no-json-parse': noJsonParse,
    'no-math-random': noMathRandom,
    'no-node-fs': noNodeFs,
    'no-throw': noThrow
  }
});
5

Write tests with Testing

effect-oxlint ships a Testing module as a dedicated subpath export. It provides mock AST node builders, a synchronous rule runner, and assertion helpers for @effect/vitest.
no-json-parse.test.ts
import { describe, expect, test } from '@effect/vitest';
import * as Option from 'effect/Option';
import * as Testing from 'effect-oxlint/testing';

describe('no-json-parse', () => {
  test('reports JSON.parse', () => {
    const result = Testing.runRule(
      noJsonParse,
      'MemberExpression',
      Testing.memberExpr('JSON', 'parse')
    );
    Testing.expectDiagnostics(result, [{ message: 'Use Schema for JSON' }]);
  });

  test('reports JSON.stringify', () => {
    const result = Testing.runRule(
      noJsonParse,
      'MemberExpression',
      Testing.memberExpr('JSON', 'stringify')
    );
    // messages() returns Option per diagnostic — useful with expect()
    expect(Testing.messages(result)).toEqual([
      Option.some('Use Schema for JSON')
    ]);
  });

  test('ignores other member expressions', () => {
    const result = Testing.runRule(
      noJsonParse,
      'MemberExpression',
      Testing.memberExpr('console', 'log')
    );
    Testing.expectNoDiagnostics(result);
  });
});
Testing.runRule takes a rule, a visitor key, and a mock AST node. The available node builders include id, memberExpr, computedMemberExpr, chainedMemberExpr, callExpr, callOfMember, importDecl, newExpr, throwStmt, tryStmt, ifStmt, program, objectExpr, and more.
Import Testing from effect-oxlint/testing, not from the main effect-oxlint export. The testing subpath is intentionally separate so production bundles do not include test-only code.

Next steps

Visitor combinators

Compose, merge, filter, and accumulate visitors for complex traversal logic.

AST matching

Use Option-returning matchers with the dual API for safe, composable queries.

Diagnostics and autofixes

Build structured diagnostics with composable autofix functions.

Installation

Runtime support details for Bun, Deno, and Node.js.

Build docs developers (and LLMs) love