logoESLint React
Custom Rules

Introduction

Build custom ESLint rules for React using the eslint-react toolkit.

This utility module is for advanced use cases where you need to build custom ESLint rules tailored to your project's specific requirements.

Installation

npm install --save-dev @eslint-react/kit

Quick Start

Define custom rules directly in your eslint.config.ts:

import eslintJs from "@eslint/js";
import { defineConfig as defineReactConfig, merge } from "@eslint-react/kit";
import { defineConfig } from "eslint/config";
import tseslint from "typescript-eslint";

export default defineConfig(
  {
    files: ["**/*.{ts,tsx}"],
    extends: [
      eslintJs.configs.recommended,
      tseslint.configs.recommended,
      defineReactConfig(
        {
          name: "function-component-definition",
          make: (ctx, kit) => {
            const { query, visitor } = kit.collect.components(ctx);

            return merge(
              visitor,
              {
                "Program:exit"(program) {
                  for (const { node } of query.all(program)) {
                    if (node.type === "ArrowFunctionExpression") continue;
                    ctx.report({
                      node,
                      message: "Function components must be defined with arrow functions.",
                    });
                  }
                },
              },
            );
          },
        },
      ),
    ],
  },
);

Rule Structure

Each custom rule is an object with name, optional meta, and a make function:

import { merge } from "@eslint-react/kit";

{
  name: "rule-name",
  make: (ctx, kit) => {
    // Create a collector — returns { query, visitor }
    const { query, visitor } = kit.collect.components(ctx);

    // Merge the collector's visitor with your inspection visitor
    return merge(
      visitor,
      {
        "Program:exit"(program) {
          // Access collected semantic nodes via query
          for (const { node } of query.all(program)) {
            ctx.report({
              node,
              message: "Your error message",
            });
          }
        },
      },
    );
  },
}

Toolkit Reference

The kit object is passed as the second argument to make. It exposes four domains:

kit.collect — Semantic Collectors

MethodDescription
components(ctx, options?)Detects function components. Options: { hint?: bigint, collectDisplayName?: boolean }
hooks(ctx)Detects custom hook definitions.

Each collector returns { query, visitor }. The visitor must be merged into your rule listener via merge(). After traversal, query.all(program) yields all detected semantic nodes.

Both collectors also expose query.current() and query.currentStack() for accessing traversal context during the AST walk.

kit.is — Predicates

All predicates live under a single unified kit.is namespace.

Component:

PredicateDescription
componentDefinitionWhether a function node is a component definition.
componentNameStrict PascalCase component name check.
componentNameLooseLoose component name check.
componentWrapperCallWhether a node is a memo(…) or forwardRef(…) call.
componentWrapperCallLooseLike above, but also matches useCallback(…).
componentWrapperCallbackWhether a function is the callback passed to a wrapper.

Hook — general:

PredicateDescription
hookWhether a function node is a hook (by name).
hookCallWhether a node is a hook call.
hookNameWhether a string matches the use[A-Z] convention.
useEffectLikeCallWhether a node is a useEffect/useLayoutEffect-like call.
useStateLikeCallWhether a node is a useState-like call.
useEffectSetupCallbackWhether a node is a useEffect setup function.
useEffectCleanupCallbackWhether a node is a useEffect cleanup function.

Hook — built-in call predicates:

Each takes a TSESTree.Node | null and returns true if the node is a call to that specific hook:

useCall · useActionStateCall · useCallbackCall · useContextCall · useDebugValueCall · useDeferredValueCall · useEffectCall · useFormStatusCall · useIdCall · useImperativeHandleCall · useInsertionEffectCall · useLayoutEffectCall · useMemoCall · useOptimisticCall · useReducerCall · useRefCall · useStateCall · useSyncExternalStoreCall · useTransitionCall

CallExpression(node) {
  if (kit.is.useStateCall(node)) {
    // node is a useState(...) call
  }
}

React API — factories:

PredicateDescription
reactAPIFactory: creates a predicate for a React API identifier.
reactAPICallFactory: creates a predicate for a React API call.

React API — pre-built identifier predicates (support both data-first and data-last):

captureOwnerStack · childrenCount · childrenForEach · childrenMap · childrenOnly · childrenToArray · cloneElement · createContext · createElement · forwardRef · memo · lazy

React API — pre-built call predicates:

captureOwnerStackCall · childrenCountCall · childrenForEachCall · childrenMapCall · childrenOnlyCall · childrenToArrayCall · cloneElementCall · createContextCall · createElementCall · forwardRefCall · memoCall · lazyCall

// data-first
kit.is.memo(ctx, node);
kit.is.memoCall(ctx, node);

// data-last (useful in filter/find)
nodes.filter(kit.is.memoCall(ctx));

// factory for any API name
const isCreateRefCall = kit.is.reactAPICall("createRef");
isCreateRefCall(ctx, node);

Import source:

PredicateDescription
initializedFromReactWhether a variable comes from a React import.
initializedFromReactNativeWhether a variable comes from a React Native import.

kit.hint & kit.flag — Bit-Flags

kit.hint.component provides flags to customize what the component collector considers a "component". kit.hint.defaultComponent is the default hint used when none is specified. Combine with | and remove with & ~:

// Also treat object methods as components
const hint = kit.hint.defaultComponent
  & ~kit.hint.component.DoNotIncludeFunctionDefinedAsObjectMethod;

const { query, visitor } = kit.collect.components(ctx, { hint });

kit.flag.component provides flags indicating component characteristics:

for (const component of query.all(program)) {
  if (component.flag & kit.flag.component.Memo) {
    // This component is wrapped in React.memo
  }
}

kit.builtinHookNames

A readonly array of all React built-in hook names ("use", "useState", "useEffect", …). Useful for building custom validation logic against the full list.

Rule Recipes

Browse our collection of copy-paste ready rule recipes:

Resources

  • AST Explorer - A tool for exploring the abstract syntax tree (AST) of JavaScript code, which is essential for writing ESLint rules.
  • ESLint Developer Guide - Official ESLint documentation for creating custom rules.

On this page