Try @eslint-react/kit Beta →
logoESLint React

Migrating from eslint-plugin-react

Complete guide for migrating from eslint-plugin-react to ESLint React

This guide provides a comprehensive comparison between eslint-plugin-react and ESLint React rules to help you migrate your existing configuration.

Overview

ESLint React is designed as a modern replacement for eslint-plugin-react with improved performance, better TypeScript support, and more accurate rule implementations. However, not all rules have direct equivalents, and some behave differently.

Rule Comparison Table

Legend

  • 🔧 Fully supported - Rule is supported, and has an auto-fix
  • Mostly supported - Rule is supported but doesn't have an auto-fix
  • 🟡 Partial support - Similar but not identical functionality
  • Not supported - No equivalent rule
  • ➡️ External plugin - Rule is available in another ESLint plugin
  • 🚫 Legacy - Rule is not applicable in modern TypeScript React development (e.g., class-based components, propTypes)
  • ⚠️ Warning - Rule has been deprecated in eslint-plugin-react
  • 🔄 Codemod - Rule is supported as a codemod (safe AST transformation)

A variety of rules are marked legacy, but still have equivalent rules. This distinction was done to more accurately assess migration for React written with function components and no longer use propTypes.

Table

The following table compares all rules from eslint-plugin-react with their ESLint React (or external) equivalents:

eslint-plugin-react Rule NameNew Rule NamePrev StatusStatus
boolean-prop-naming@eslint-react/kit/boolean-prop-naming report-only
button-has-typedom-no-missing-button-type🔧
checked-requires-onchange-or-readonly@eslint-react/kit/checked-requires-onchange-or-readonly report-only
default-props-match-prop-types🚫
destructuring-assignmentN/A (use @eslint-react/kit to implement as a custom rule instead)🔧
display-nameno-missing-component-display-name + no-missing-context-display-name🟡
forbid-component-props@eslint-react/kit/forbid-component-props report-only
forbid-dom-props@eslint-react/kit/forbid-dom-props report-only
forbid-elements@eslint-react/kit/forbid-elements report-only
forbid-foreign-prop-types🚫
forbid-prop-types🚫
forward-ref-uses-refno-forward-ref🔄
function-component-definition@eslint-react/kit (custom rule, e.g. @eslint-react/kit/function-component-definition; report-only by default, autofix depends on your implementation)🔧
hook-use-stateuse-state
iframe-missing-sandboxdom-no-missing-iframe-sandbox🔧
jsx-boolean-value@eslint-react/kit/jsx-boolean-value includes autofix🔧🔧
jsx-child-element-spacing@stylistic/jsx-child-element-spacing➡️
jsx-closing-bracket-location@stylistic/jsx-closing-bracket-location🔧➡️
jsx-closing-tag-location@stylistic/jsx-closing-tag-location🔧➡️
jsx-curly-brace-presence@stylistic/jsx-curly-brace-presence🔧➡️
jsx-curly-newline@stylistic/jsx-curly-newline🔧➡️
jsx-curly-spacing@stylistic/jsx-curly-spacing🔧➡️
jsx-equals-spacing@stylistic/jsx-equals-spacing🔧➡️
jsx-first-prop-new-line@stylistic/jsx-first-prop-new-line🔧➡️
jsx-fragments@eslint-react/kit/jsx-fragments🔧🔧
jsx-handler-names@eslint-react/kit/jsx-handler-names
jsx-indent@stylistic/jsx-indent🔧➡️
jsx-indent-props@stylistic/jsx-indent-props🔧➡️
jsx-keyno-missing-key + no-duplicate-key + no-implicit-key
jsx-max-depth@eslint-react/kit/jsx-max-depth
jsx-max-props-per-line@stylistic/jsx-max-props-per-line🔧➡️
jsx-newline@stylistic/jsx-newline🔧➡️
jsx-no-bind@eslint-react/kit/jsx-no-bind; report-only
jsx-no-comment-textnodesjsx-no-comment-textnodes
jsx-no-constructed-context-valuesno-unstable-context-value
jsx-no-duplicate-props@eslint-react/kit/jsx-no-duplicate-props
jsx-no-leaked-renderno-leaked-conditional-rendering🔧
jsx-no-literals@eslint-react/kit/jsx-no-literals
jsx-no-script-urldom-no-script-url
jsx-no-target-blankdom-no-unsafe-target-blank🔧
jsx-no-undefN/A (ESLint v10.0.0+ now tracks JSX references natively)
jsx-no-useless-fragmentjsx-no-useless-fragment🔧
jsx-one-expression-per-line@stylistic/jsx-one-expression-per-line🔧➡️
jsx-pascal-case@eslint-react/kit/jsx-pascal-case
jsx-props-no-multi-spaces@stylistic/jsx-props-no-multi-spaces🔧➡️
jsx-props-no-spread-multi@eslint-react/kit/jsx-props-no-spread-multi
jsx-props-no-spreading@eslint-react/kit/jsx-props-no-spreading; report-only
jsx-sort-default-props⚠️
jsx-sort-props@stylistic/jsx-sort-props🔧➡️
jsx-space-before-closing⚠️
jsx-tag-spacing@stylistic/jsx-tag-spacing🔧➡️
jsx-uses-reactN/A (ESLint v10.0.0+ now tracks JSX references natively)
jsx-uses-varsN/A (ESLint v10.0.0+ now tracks JSX references natively)
jsx-wrap-multilines@stylistic/jsx-wrap-multilines🔧➡️
no-access-state-in-setstateno-access-state-in-setstate
no-adjacent-inline-elements@eslint-react/kit/no-adjacent-inline-elements
no-array-index-keyno-array-index-key
no-arrow-function-lifecycle🔧🚫
no-children-propjsx-no-children-prop
no-dangerdom-no-dangerously-set-innerhtml
no-danger-with-childrendom-no-dangerously-set-innerhtml-with-children
no-deprecatedno-component-will-mount + no-component-will-receive-props + no-component-will-update + no-create-ref + no-forward-ref + dom-no-render + dom-no-render-return-value + dom-no-hydrate + dom-no-find-dom-node🟡
no-did-mount-set-stateno-set-state-in-component-did-mount🚫
no-did-update-set-stateno-set-state-in-component-did-update🚫
no-direct-mutation-stateno-direct-mutation-state🚫
no-find-dom-nodedom-no-find-dom-node
no-invalid-html-attributeSee also dom-no-unknown-property
no-is-mounted🚫
no-multi-comp@eslint-react/kit/no-multi-comp; report-only
no-namespacejsx-no-namespace
no-object-type-as-default-propno-unstable-default-props
no-render-return-valuedom-no-render-return-value
no-set-state🚫
no-string-refs🚫
no-this-in-sfc🚫
no-typos
no-unknown-propertydom-no-unknown-property🔧🔧
no-unsafeno-unsafe-component-will-mount + no-unsafe-component-will-receive-props + no-unsafe-component-will-update🟡
no-unstable-nested-componentsno-nested-component-definitions
no-unused-class-component-methodsno-unused-class-component-members🚫
no-unused-prop-typesno-unused-props🚫
no-unused-stateN/A (Rule deprecated due to low usage and overlap with other rules)
no-will-update-set-stateno-set-state-in-component-will-update
prefer-es6-class🚫
prefer-exact-props🚫
prefer-read-only-props🔧🚫
prefer-stateless-function🚫
prop-types🚫
react-in-jsx-scope🚫
require-default-props🚫
require-optimization🚫
require-render-return🚫
self-closing-comp@stylistic/jsx-self-closing-comp🔧➡️
sort-comp🚫
sort-default-props🚫
sort-prop-types🔧🚫
state-in-constructor🚫
static-property-placement🚫
style-prop-objectdom-no-string-style-prop
void-dom-elements-no-childrendom-no-void-elements-with-children

ESLint React Column

  • Rule names link to ESLint React documentation
  • Multiple rules separated by / indicate alternative approaches
  • Rules with + indicate multiple rules that together provide equivalent functionality

Gradual Migration

You can migrate gradually by using both plugins together, using the disable-conflict-eslint-plugin-react ruleset:

eslint.config.ts
import eslintReact from "@eslint-react/eslint-plugin";
import pluginReact from "eslint-plugin-react";
import { defineConfig } from "eslint/config";

export default defineConfig([
  // Start with the eslint-plugin-react
  {
    files: ["**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts}"],
    extends: [
      // Whatever config you had enabled with eslint-plugin-react
      pluginReact.configs.flat.recommended,

      // Now disable all conflicting rules
      eslintReact.configs["disable-conflict-eslint-plugin-react"],

      // Now enable the desired rules
      eslintReact.configs["recommended-typescript"],
    ],
  },
]);

Once you have fully migrated, you can remove eslint-plugin-react entirely and rely solely on ESLint React:

eslint.config.ts
import eslintReact from "@eslint-react/eslint-plugin";
import { defineConfig } from "eslint/config";

export default defineConfig([
  {
    files: ["**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts}"],
    extends: [
      eslintReact.configs["recommended-typescript"],
    ],
  },
]);

Custom Rules For Missing Rules

Some eslint-plugin-react rules don't have built-in equivalents in ESLint React, or you may want to customize their behavior. You can use @eslint-react/kit to create minimal custom rule implementations directly in your eslint.config.ts.

Install @eslint-react/kit first:

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

Below are drop-in rule definitions for the most commonly needed rules. Register them via the .use() chain in your config:

eslint.config.ts
import  from "@eslint-react/eslint-plugin";
import  from "@eslint-react/kit";
import {  } from "eslint/config";
import {
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  ,
  ,
} from "@examples/react-dom-with-custom-rules";

export default ([
  {
    : ["**/*.{ts,tsx}"],
    : [
      .["recommended-typescript"],
      ()
        .(, { : "^(is|has|should)[A-Z]([A-Za-z0-9]?)+" })
        .()
        .(, { : ["className", "style"] })
        .(, { : ["style", "className"] })
        .()
        .(, { : "handle", : "on" })
        .(, { : 3 })
        .()
        .(, { : true, : ["allowed"] })
        .()
        .()
        .()
        .()
        .(, {
          : new ([
            ["button", "Use <Button> from '@/components/ui' instead."],
            ["input", "Use <Input> from '@/components/ui' instead."],
          ]),
        })
        .()
        .()
        .()
        .()
        .(),
    ],
  },
]);

boolean-prop-naming

Enforce a naming convention for boolean props.

.config/booleanPropNaming.ts
import { type ,  } from "@eslint-react/kit";
import {  } from "@typescript-eslint/utils";
import ts from "typescript";

// ── Options ───────────────────────────────────────────
export type  = {
  /** A regular expression that boolean prop names must match. */
  ?: string;
};

// ── Constants ─────────────────────────────────────────
const  = "^(is|has|should)[A-Z]([A-Za-z0-9]?)+";
const  = ts.. | ts..;

// ── Type Utils ────────────────────────────────────────
const  = (: ts.Type): ts.Type[] =>
  .() ? ..() : [];

const  = (: ts.Type): boolean =>
  ().( => !!(.() & ));

// ── Rule ──────────────────────────────────────────────
/** Enforce boolean prop naming convention. */
export function (
  ?: ,
):  {
  const {  =  } =  ?? {};
  const  = new ();

  // ── Listener ────────────────────────────────────────
  return (, {  }) => {
    const  = .(, false); // parser services
    const  = ..();                  // type checker
    const { ,  } = .();    // component utils

    return (, {
      "Program:exit"() {
        const  = .();

        // ── Each component ────────────────────────────
        for (const  of ) {
          const [] = ..;
          if ( == null) continue; // no props

          const  = ..();
          const  = .();
          const  = .();

          // ── Each prop ───────────────────────────────
          for (const  of ) {
            const  = .(, );
            if (!()) continue; // non-boolean
            if (.(.))    continue; // valid name

            const  = .();
            if ( == null)            continue; // no declarations

            const [] = ;
            if ( == null)             continue; // empty

            const  = ..();
            if ( == null)         continue; // no estree node

            const  = "key" in  ? . : ;

            .({
              : { : .,  },
              : `Boolean prop "{{name}}" should match "{{rule}}".`,
              ,
            });
          }
        }
      },
    });
  };
}

checked-requires-onchange-or-readonly

Require onChange or readOnly when using checked on <input>.

.config/checkedRequiresOnchangeOrReadonly.ts
import type {  } from "@eslint-react/kit";

/** Require `onChange` or `readOnly` when using `checked` on `<input>`. */
export function ():  {
  return () => ({
    () {
      const  = .. === "JSXIdentifier" ? .. : null;
      if ( !== "input") return;
      const  = new <string>();
      for (const  of .) {
        if (. === "JSXAttribute" && .. === "JSXIdentifier") {
          .(..);
        }
      }
      if (!.("checked")) return;
      if (!.("onChange") && !.("readOnly")) {
        .({
          ,
          : "`checked` requires `onChange` or `readOnly`.",
        });
      }
    },
  });
}

forbid-component-props

Forbid certain props on React components (not DOM elements). Only reports on PascalCase elements.

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

/** Options for {@link forbidComponentProps}. */
export type  = {
  /** Prop names that are not allowed on React components. */
  : string[];
};

/** Forbid certain props on React components (not DOM elements). */
export function (: ):  {
  const {  } = ;
  return () => ({
    () {
      const  = .. === "JSXIdentifier" ? .. : null;
      if ( == null || !.()) return;
      const  = .;
      if (?. !== "JSXOpeningElement") return;
      const  = .. === "JSXIdentifier" ? .. : null;
      // Only report on components (PascalCase names), not DOM elements
      if ( == null || [0] !== [0]?.()) return;
      .({
        ,
        : `Prop "${}" is forbidden on components.`,
      });
    },
  });
}

forbid-dom-props

Forbid certain props on DOM elements (not React components). Only reports on lowercase DOM element names.

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

/** Options for {@link forbidDomProps}. */
export type  = {
  /** Prop names that are not allowed on DOM elements. */
  : string[];
};

/** Forbid certain props on DOM elements (not React components). */
export function (: ):  {
  const {  } = ;
  return () => ({
    () {
      const  = .. === "JSXIdentifier" ? .. : null;
      if ( == null || !.()) return;
      const  = .;
      if (?. !== "JSXOpeningElement") return;
      const  = .. === "JSXIdentifier" ? .. : null;
      // Only report on DOM elements (lowercase names), not components
      if ( == null || [0] !== [0]?.()) return;
      .({
        ,
        : `Prop "${}" is forbidden on DOM elements.`,
      });
    },
  });
}

forbid-elements

Forbid specific JSX elements. Customize the forbidden map with your project's requirements.

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

/** Options for {@link forbidElements}. */
export type  = {
  /** A map from element name to the error message reported when that element is used. */
  : <string, string>;
};

/** Forbid specific JSX elements. */
export function (: ):  {
  const {  } = ;
  return () => ({
    () {
      const  = .. === "JSXIdentifier" ? .. : null;
      if ( != null && .()) {
        .({ , : .()! });
      }
    },
  });
}

function-component-definition

Enforce arrow function definitions for function components.

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

/** Enforce arrow function definitions for function components. */
export function ():  {
  return (, { ,  }) => {
    const { ,  } = .(, {
      : .. & ~..,
    });
    return (
      ,
      {
        "Program:exit"() {
          for (const {  } of .()) {
            if (. === "ArrowFunctionExpression") continue;
            .({
              ,
              : "Function components must be defined with arrow functions.",
              : [
                {
                  : "Convert to arrow function.",
                  () {
                    const  = .;
                    if (.) return null;
                    const  = . ? "async " : "";
                    const  = . ? .(.) : "";
                    const  = `(${..(() => .()).(", ")})`;
                    const  = . ? .(.) : "";
                    const  = .(.);

                    // function Foo(params) { ... } -> const Foo = (params) => { ... };
                    if (. === "FunctionDeclaration" && .) {
                      // dprint-ignore
                      return .(, `const ${..} = ${}${}${}${} => ${};`);
                    }

                    // const Foo = function(params) { ... } -> const Foo = (params) => { ... }
                    if (. === "FunctionExpression" && .. === "VariableDeclarator") {
                      // dprint-ignore
                      return .(, `${}${}${}${} => ${}`);
                    }

                    // { Foo(params) { ... } } -> { Foo: (params) => { ... } }
                    if (. === "FunctionExpression" && .. === "Property") {
                      // dprint-ignore
                      return .(., `${.(..)}: ${}${}${}${} => ${}`);
                    }

                    return null;
                  },
                },
              ],
            });
          }
        },
      },
    );
  };
}

jsx-boolean-value

Enforce shorthand for boolean JSX attributes (e.g. prefer <C disabled /> over <C disabled={true} />).

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

/** Enforce shorthand for boolean JSX attributes. */
export function ():  {
  return () => ({
    () {
      const {  } = ;
      if (?. !== "JSXExpressionContainer") return;
      if (.. !== "Literal" || .. !== true) return;
      .({
        ,
        : "Omit the value for boolean attributes.",
        : () => .([..[1], .[1]]),
      });
    },
  });
}

jsx-fragments

Enforce shorthand syntax for React fragments. Reports when <React.Fragment> is used instead of <>...</>. Allows standard form when key prop is present.

import type {  } from "@eslint-react/kit";
import type {  } from "@typescript-eslint/types";

/** Options for {@link jsxFragments}. */
export type  = {
  /** The mode to enforce: "syntax" (default, shorthand) or "element" (standard form). */
  ?: "syntax" | "element";
};

/** Enforce shorthand or standard form for React fragments. */
export function (:  = {}):  {
  const {  = "syntax" } = ;
  return () => {
    function (: .JSXOpeningElement, : "React.Fragment" | "Fragment") {
      const  = .. > 0;
      if () return;
      .({
        ,
        : `Use shorthand fragment syntax '<>...</>' instead of '<${}>...</${}'.`,
        () {
          const  = .?.;
          if (!) return null;
          return [.(, "<>"), .(, "</>")];
        },
      });
    }

    return {
      () {
        const  = .;

        // Handle standalone <Fragment> (JSXIdentifier)
        if (. === "JSXIdentifier" && . === "Fragment") {
          if ( === "syntax") {
            (, "Fragment");
          }
          return;
        }

        // Handle <React.Fragment> (JSXMemberExpression)
        if (. !== "JSXMemberExpression") return;
        if (.. !== "JSXIdentifier" || .. !== "React") return;
        if (.. !== "JSXIdentifier" || .. !== "Fragment") return;

        if ( === "syntax") {
          (, "React.Fragment");
        }
      },
      () {
        if ( === "element") {
          .({
            ,
            : "Use '<React.Fragment>...</React.Fragment>' instead of shorthand '<>...</>'.",
            () {
              return [
                .(., "<React.Fragment>"),
                .(., "</React.Fragment>"),
              ];
            },
          });
        }
      },
    };
  };
}

jsx-handler-names

Enforce naming convention for JSX event handler props and the functions they reference.

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

/** Options for {@link jsxHandlerNames}. */
export type  = {
  /** Prefix for event handler functions (default: "handle"). */
  ?: string;
  /** Prefix for event handler props (default: "on"). */
  ?: string;
  /** Whether to check inline functions (default: false). */
  ?: boolean;
};

/** Enforce naming convention for JSX event handlers. */
export function (:  = {}):  {
  const {
     = "handle",
     = "on",
     = false,
  } = ;
  const  = new (`^${}[A-Z]`);
  const  = new (`^${}[A-Z]`);

  return () => ({
    () {
      if (.. !== "JSXIdentifier") return;
      const  = ..;
      if (!.()) return;

      const  = .;
      if (!) return;

      if (. === "JSXExpressionContainer") {
        const  = .;

        if (. === "Identifier") {
          const  = .;
          if (!.()) {
            .({
              : ,
              : `Handler function "${}" should be named "${}${
                .(.)
              }..."`,
            });
          }
          return;
        }

        if (. === "ArrowFunctionExpression" || . === "FunctionExpression") {
          if () {
            .({
              : ,
              :
                `Inline function handlers are not allowed for "${}". Extract it to a named "${}${
                  .(.)
                }" function.`,
            });
          }
          return;
        }
      }
    },
  });
}

jsx-max-depth

Enforce a maximum depth for JSX elements.

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

/** Options for {@link jsxMaxDepth}. */
export type  = {
  /** Maximum allowed depth for JSX elements. */
  : number;
};

/** Enforce JSX maximum depth. */
export function (: ):  {
  const {  } = ;
  return () => ({
    () {
      let  = 0;
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      let : any = .;
      while () {
        if (.type === "JSXElement") {
          ++;
        }
         = .parent;
      }
      if ( > ) {
        .({
          ,
          : `JSX element exceeds maximum depth of ${} (found ${}).`,
        });
      }
    },
  });
}

jsx-no-bind

Prevent arrow functions, function expressions, and .bind() in JSX props.

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

/** Prevent inline functions and `.bind()` in JSX props. */
export function ():  {
  return () => ({
    () {
      const  = .;
      if (?. !== "JSXExpressionContainer") return;
      switch (true) {
        case .. === "ArrowFunctionExpression":
        case .. === "FunctionExpression":
          .({ , : "JSX props should not use inline functions." });
          break;
        case .. === "CallExpression"
          && ... === "MemberExpression"
          && .... === "Identifier"
          && .... === "bind":
          .({ , : "JSX props should not use .bind()." });
          break;
      }
    },
  });
}

jsx-no-duplicate-props

Disallow duplicate properties in JSX elements.

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

/** Options for {@link jsxNoDuplicateProps}. */
export type  = {
  /** Whether to ignore case when checking for duplicate props. */
  ?: boolean;
};

/** Disallow duplicate properties in JSX. */
export function (:  = {}):  {
  const {  = false } = ;
  return () => ({
    () {
      const  = new <string, string>();
      for (const  of .) {
        if (. !== "JSXAttribute") continue;
        if (.. !== "JSXIdentifier") continue;
        const  =  ? ...() : ..;
        if (.()) {
          .({
            : ,
            : `Duplicate prop "${..}" found.`,
          });
        } else {
          .(, ..);
        }
      }
    },
  });
}

jsx-no-literals

Disallow usage of string literals in JSX. By default requires wrapping strings in JSX expressions {'TEXT'}.

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

/** Options for {@link jsxNoLiterals}. */
export type  = {
  /** Enforces no string literals used as children, wrapped or unwrapped. */
  ?: boolean;
  /** An array of unique string values that would otherwise warn, but will be ignored. */
  ?: string[];
  /** When `true` the rule ignores literals used in props. */
  ?: boolean;
};

/** Disallow usage of string literals in JSX. */
export function (:  = {}):  {
  const {  = false,  = [],  = true } = ;
  const  = new ();
  return () => ({
    () {
      if (typeof . !== "string") return;
      const  = ..();
      if ( === "" || .()) return;

      const  = .;
      if (!) return;

      if (. === "JSXAttribute") {
        if (!) {
          .({
            ,
            : `String literals are not allowed in JSX props. Use {'${}'} instead.`,
          });
        }
        return;
      }

      if (. === "JSXExpressionContainer") return;

      if (. === "JSXElement" || . === "JSXFragment") {
        if () {
          .({
            ,
            : `String literals are not allowed as JSX children.`,
          });
        } else {
          .({
            ,
            : `String literals should be wrapped in JSX expression: {'${}'}`,
          });
        }
      }
    },
    () {
      const  = ..();
      if ( === "" || .()) return;

      if () {
        .({
          ,
          : `String literals are not allowed as JSX children.`,
        });
      } else {
        .({
          ,
          : `String literals should be wrapped in JSX expression: {'${}'}`,
        });
      }
    },
  });
}

jsx-pascal-case

Enforce PascalCase for user-defined JSX components. DOM elements like <div> are ignored.

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

/** Options for {@link jsxPascalCase}. */
export type  = {
  /** Allow all-uppercase component names like `<XML />`. */
  ?: boolean;
  /** Allow leading underscores in component names like `<_Component />`. */
  ?: boolean;
};

/** Enforce PascalCase for user-defined JSX components. */
export function (:  = {}):  {
  const {  = false,  = false } = ;
  // Check PascalCase: first letter uppercase, rest can be mixed but no underscores
  const  = /^[A-Z][a-zA-Z0-9]*$/;
  return () => ({
    () {
      const  = .;
      if (. !== "JSXIdentifier") return;

      const  = .;

      // Check for leading underscore (before lowercase check since "_".toLowerCase() === "_")
      if (.("_")) {
        if (!) {
          .({
            : ,
            : `Component name "${}" should not start with an underscore.`,
          });
        }
        return;
      }

      // Ignore DOM elements (lowercase first letter)
      const  = [0];
      if ( === ) return;
      if ( === .()) return;

      // Check for all caps
      if ( === .()) {
        if (!) {
          .({
            : ,
            : `Component name "${}" should use PascalCase, not all uppercase.`,
          });
        }
        return;
      }

      if (!.()) {
        .({
          : ,
          : `Component name "${}" should be in PascalCase.`,
        });
      }
    },
  });
}

jsx-props-no-spread-multi

Disallow spreading the same expression multiple times in a JSX element.

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

/** Disallow JSX prop spreading the same identifier multiple times. */
export function ():  {
  return () => ({
    () {
      const  = new <string>();
      for (const  of .) {
        if (. !== "JSXSpreadAttribute") continue;

        let : string;
        if (.. === "Identifier") {
           = ..;
        } else {
           = ..(.);
        }

        if (.()) {
          .({
            : ,
            : `Spreading the same expression "${}" multiple times is not allowed.`,
          });
        } else {
          .();
        }
      }
    },
  });
}

jsx-props-no-spreading

Disallow JSX props spreading.

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

/** Disallow JSX props spreading. */
export function ():  {
  return () => ({
    () {
      .({
        ,
        : "Props spreading is not allowed.",
      });
    },
  });
}

no-adjacent-inline-elements

Disallow adjacent inline elements not separated by whitespace.

import type {  } from "@eslint-react/kit";
import type {  } from "@typescript-eslint/types";

/** Disallow adjacent inline elements not separated by whitespace. */
export function ():  {
  /** Set of inline HTML elements. */
  const  = new ([
    "a",
    "abbr",
    "acronym",
    "b",
    "bdi",
    "bdo",
    "big",
    "br",
    "cite",
    "code",
    "dfn",
    "em",
    "i",
    "img",
    "input",
    "kbd",
    "label",
    "map",
    "object",
    "q",
    "samp",
    "script",
    "select",
    "small",
    "span",
    "strong",
    "sub",
    "sup",
    "textarea",
    "time",
    "tt",
    "var",
  ]);

  return () => ({
    () {
      const  = .;
      for (let  = 0;  < . - 1; ++) {
        const  = [];
        const  = [ + 1];

        if (?. !== "JSXElement") continue;
        if (... !== "JSXIdentifier") continue;
        const  = ...;
        if (!.()) continue;

        if (?. !== "JSXElement") continue;
        if (... !== "JSXIdentifier") continue;
        const  = ...;
        if (!.()) continue;

        .({
          : ,
          : `Adjacent inline elements "${}" and "${}" should be separated by whitespace.`,
        });
      }
    },
  });
}

no-multi-comp

Prevent defining more than one component per file.

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

/** Prevent defining more than one component per file. */
export function ():  {
  return (, {  }) => {
    const { ,  } = .();
    return (, {
      "Program:exit"() {
        const  = .();
        for (const { ,  } of .(1)) {
          .({
            ,
            : `Declare only one component per file. Found extra component "${ ?? "anonymous"}".`,
          });
        }
      },
    });
  };
}