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/kitQuick 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
| Method | Description |
|---|---|
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:
| Predicate | Description |
|---|---|
componentDefinition | Whether a function node is a component definition. |
componentName | Strict PascalCase component name check. |
componentNameLoose | Loose component name check. |
componentWrapperCall | Whether a node is a memo(…) or forwardRef(…) call. |
componentWrapperCallLoose | Like above, but also matches useCallback(…). |
componentWrapperCallback | Whether a function is the callback passed to a wrapper. |
Hook — general:
| Predicate | Description |
|---|---|
hook | Whether a function node is a hook (by name). |
hookCall | Whether a node is a hook call. |
hookName | Whether a string matches the use[A-Z] convention. |
useEffectLikeCall | Whether a node is a useEffect/useLayoutEffect-like call. |
useStateLikeCall | Whether a node is a useState-like call. |
useEffectSetupCallback | Whether a node is a useEffect setup function. |
useEffectCleanupCallback | Whether 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:
| Predicate | Description |
|---|---|
reactAPI | Factory: creates a predicate for a React API identifier. |
reactAPICall | Factory: 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:
| Predicate | Description |
|---|---|
initializedFromReact | Whether a variable comes from a React import. |
initializedFromReactNative | Whether 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:
- Function Component Definition - Enforce arrow functions for components
- No createRef - Disallow
React.createRef()in favor ofuseRef
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.