🚨 Active Supply Chain Attack:node-ipc Package Compromised.Learn More
Socket
Book a DemoSign in
Socket

@eslint-react/kit

Package Overview
Dependencies
Maintainers
1
Versions
771
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@eslint-react/kit - npm Package Compare versions

Comparing version
5.2.4-beta.2
to
5.2.4-beta.3
+1
-1
dist/index.js

@@ -9,3 +9,3 @@ import * as core from "@eslint-react/core";

var name = "@eslint-react/kit";
var version = "5.2.4-beta.2";
var version = "5.2.4-beta.3";

@@ -12,0 +12,0 @@ //#endregion

{
"name": "@eslint-react/kit",
"version": "5.2.4-beta.2",
"version": "5.2.4-beta.3",
"description": "ESLint React's utility module for building custom React rules with JavaScript functions.",

@@ -41,4 +41,4 @@ "keywords": [

"string-ts": "^2.3.1",
"@eslint-react/core": "5.2.4-beta.2",
"@eslint-react/shared": "5.2.4-beta.2"
"@eslint-react/core": "5.2.4-beta.3",
"@eslint-react/shared": "5.2.4-beta.3"
},

@@ -48,6 +48,6 @@ "devDependencies": {

"tsdown": "^0.21.9",
"@eslint-react/ast": "5.2.4-beta.2",
"@local/eff": "3.0.0-beta.72",
"@eslint-react/eslint": "5.2.4-beta.2",
"@local/configs": "0.0.0"
"@eslint-react/ast": "5.2.4-beta.3",
"@eslint-react/eslint": "5.2.4-beta.3",
"@local/configs": "0.0.0",
"@local/eff": "3.0.0-beta.72"
},

@@ -54,0 +54,0 @@ "peerDependencies": {

+1
-656
# @eslint-react/kit
ESLint React's toolkit for building custom React rules with JavaScript functions right inside your `eslint.config.ts`.
## Index
- [Installation](#installation)
- [Quick Start](#quick-start)
- [API Reference](#api-reference)
- [`eslintReactKit` (default export)](#eslintreactkit-default-export)
- [`RuleFunction`](#rulefunction)
- [`Builder`](#builder)
- [`getConfig`](#getconfig)
- [`getPlugin`](#getplugin)
- [`merge`](#merge)
- [`RuleToolkit` — the toolkit object](#ruletoolkit--the-toolkit-object)
- [`collect`](#collect) — Semantic collectors
- [`is`](#is) — Predicates
- [`hint`](#hint) — Detection hint bit-flags
- [`flag`](#flag) — Component characteristic flags
- [`settings`](#settings) — Normalized ESLint React settings
- [Examples](#examples)
- [Simple: Ban `forwardRef`](#simple-ban-forwardref)
- [Component: Destructure component props](#component-destructure-component-props)
- [Hooks: Warn on custom hooks that don't call other hooks](#hooks-warn-on-custom-hooks-that-dont-call-other-hooks)
- [Multiple Collectors: No component/hook factories](#multiple-collectors-no-componenthook-factories)
- [Override Config: Using spread syntax with `getConfig`](#override-config-using-spread-syntax-with-getconfig)
- [Advanced Config: Using `getPlugin` for custom plugin namespace](#advanced-config-using-getplugin-for-custom-plugin-namespace)
- [More Examples](#more-examples)
## Installation
```sh
npm install --save-dev @eslint-react/kit
```
## Quick Start
```ts
import eslintReact from "@eslint-react/eslint-plugin";
import eslintReactKit, { merge } from "@eslint-react/kit";
import type { RuleFunction } from "@eslint-react/kit";
import eslintJs from "@eslint/js";
import { defineConfig } from "eslint/config";
import tseslint from "typescript-eslint";
/** Enforce function declarations for function components. */
function functionComponentDefinition(): RuleFunction {
return (context, { collect }) => {
const { query, visitor } = collect.components(context);
return merge(
visitor,
{
"Program:exit"(program) {
for (const { node } of query.all(program)) {
if (node.type === "FunctionDeclaration") continue;
context.report({
node,
message: "Function components must be defined with function declarations.",
});
}
},
},
);
};
}
export default defineConfig(
{
files: ["**/*.{ts,tsx}"],
extends: [
eslintJs.configs.recommended,
tseslint.configs.recommended,
eslintReact.configs["recommended-typescript"],
eslintReactKit()
.use(functionComponentDefinition)
.getConfig(),
],
},
);
```
The rule name is derived automatically from the function name (`functionComponentDefinition` → `function-component-definition`), and registered as `@eslint-react/kit/function-component-definition` at `"error"` severity.
## API Reference
### `eslintReactKit` (default export)
```ts
import eslintReactKit from "@eslint-react/kit";
eslintReactKit(): Builder
```
Creates a `Builder` instance for registering custom rules via the chainable `.use()` API.
### `RuleFunction`
```ts
import type { RuleFunction } from "@eslint-react/kit";
type RuleFunction = (context: RuleContext, toolkit: RuleToolkit) => RuleListener;
```
A function that receives the ESLint rule context and the structured `RuleToolkit` toolkit, and returns a `RuleListener` (AST visitor object).
Rules are defined as **named functions** that return a `RuleFunction`. The function name is automatically converted to kebab-case and used as the rule name under the `@eslint-react/kit` plugin namespace.
```ts
// Function name `noForwardRef` → rule name `no-forward-ref`
// Registered as `@eslint-react/kit/no-forward-ref`
function noForwardRef(): RuleFunction {
return (context, { is }) => ({ ... });
}
// Functions that accept options work the same way
function forbidElements({ forbidden }: ForbidElementsOptions): RuleFunction {
return (context) => ({ ... });
}
```
#### Anonymous Rules
When you use an **anonymous function** (arrow function without a name) with `.use()`, a random ULID is automatically generated as the rule name:
```ts
// Anonymous rule → random ULID name like "01KNE2WSJ8011D2HXE3A6H717C"
// Registered as `@eslint-react/kit/01KNE2WSJ8011D2HXE3A6H717C`
eslintReactKit().use(() => (context) => ({
JSXOpeningElement(node) {
// Critical check that cannot be easily disabled
},
}));
```
> **Note:** The rule names are ULIDs generated randomly on each ESLint run. The examples above illustrate the format — actual values will differ every time.
Anonymous rules are ideal for checks that are **critical to code quality or security** and should never be bypassed via disable comments:
```ts
// This critical security check cannot be easily disabled by developers
eslintReactKit().use(() => (context, { is }) => ({
CallExpression(node) {
// Prevent dangerous API calls that could lead to XSS
if (is.createElementCall(node) && isUnsafeArguments(node.arguments)) {
context.report({
node,
message: "Potential XSS vulnerability detected. This issue must be fixed.",
});
}
},
}));
```
**Note:** Since the rule name is random and changes on every ESLint run, developers cannot use standard rules configs or disable comments like:
```ts
// This will NOT work - the rule name is random!
{ rules: { "@eslint-react/kit/01KNE2WSJ8011D2HXE3A6H717C": "off" } }
// This will NOT work - the rule name is random!
// eslint-disable-next-line @eslint-react/kit/01KNE2WSJ8011D2HXE3A6H717C
```
To disable an anonymous rule, developers must modify the ESLint configuration file directly, which provides an audit trail for policy violations.
### `Builder`
```ts
interface Builder {
use<F extends (...args: any[]) => RuleFunction>(factory: F, ...args: Parameters<F>): Builder;
getConfig(): Linter.Config;
getPlugin(): ESLint.Plugin;
}
```
A chainable builder for registering custom rules.
| Method | Description |
| ----------- | -------------------------------------------------------------------------------------------------------------------------- |
| `use` | Registers a rule factory. The rule name is `kebabCase(factory.name)`. Options type is inferred from the factory signature. |
| `getConfig` | Returns a `Linter.Config` with all registered rules enabled at `"error"` severity. |
| `getPlugin` | Returns an `ESLint.Plugin` containing the registered rules and plugin metadata. |
#### `getConfig`
Returns a flat `Linter.Config` object with all registered rules set to `"error"`. This is a convenience wrapper that calls `getPlugin()` internally and adds the plugin plus rule entries to the config.
```ts
eslintReactKit()
.use(noForwardRef) // no-arg factory
.use(version, "19") // factory with inferred options
.getConfig();
```
#### `getPlugin`
Returns an `ESLint.Plugin` object containing the registered rules and plugin metadata (`name` and `version`). Use this when you need finer-grained control over how the plugin is integrated into your ESLint configuration — for example, when you want to choose the plugin namespace, set per-rule severities, or compose the plugin with other configs manually.
```ts
const kit = eslintReactKit()
.use(noForwardRef)
.use(version, "19");
// Retrieve the raw plugin object
const plugin = kit.getPlugin();
// Use it in a custom flat config with your own namespace and severity
export default [
{
files: ["**/*.{ts,tsx}"],
plugins: {
react: plugin,
},
rules: {
"react/version": "error",
"react/no-forward-ref": "error",
},
},
];
```
### `merge`
```ts
import { merge } from "@eslint-react/kit";
merge(...listeners: RuleListener[]): RuleListener
```
Merges multiple `RuleListener` (visitor) objects into a single listener. When two or more listeners define the same visitor key, the handlers are chained and execute in order.
This is essential for combining a collector's `visitor` with your own inspection logic.
### `RuleToolkit` — the toolkit object
The second argument passed to the `RuleFunction` function is a structured `RuleToolkit` object:
```
kit
├── collect -> Semantic collectors (components, hooks)
├── is -> All predicates (component, hook, React API, import source)
├── hint -> Detection hint bit-flags
├── flag -> Component characteristic bit-flags
├── settings -> Normalized ESLint React settings
```
---
#### `collect`
Collector factories create a `{ query, visitor }` pair. The `visitor` must be merged into your rule listener via `merge()`. After traversal completes, `query.all(program)` yields all detected semantic nodes.
| Method | Returns | Description |
| ------------------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------- |
| `components(context, options?)` | `CollectorWithContext<FunctionComponentSemanticNode>` | Detects function components. Options: `{ hint?: bigint, collectDisplayName?: boolean }` |
| `hooks(context)` | `CollectorWithContext<HookSemanticNode>` | Detects custom hook definitions. |
**`CollectorWithContext`** extends `Collector` with contextual queries:
| Query | Description |
| -------------------- | ----------------------------------------- |
| `query.all(program)` | All collected semantic nodes in the file. |
---
#### `is`
All predicates live under `kit.is` — organized into four sub-sections.
##### Component
| Predicate | Signature | Description |
| -------------------------- | ------------------------- | --------------------------------------------------------------------------- |
| `componentDecl` | `(node, hint) -> boolean` | Whether a function node is a component. (context pre-bound) |
| `componentName` | `(name) -> boolean` | Strict PascalCase component name check. |
| `componentNameLoose` | `(name) -> boolean` | Loose component name check. |
| `componentWrapperCall` | `(node) -> boolean` | Whether a node is a `memo(…)` or `forwardRef(…)` call. (context pre-bound) |
| `componentWrapperCallback` | `(node) -> boolean` | Whether a function is the callback passed to a wrapper. (context pre-bound) |
##### Hook
General hook predicates:
| Predicate | Signature | Description |
| -------------------------- | ------------------------------------- | ------------------------------------------------------------ |
| `hookDecl` | `(node) -> boolean` | Whether a function node is a hook (by name). |
| `hookCall` | `(node) -> boolean` | Whether a node is a hook call. |
| `hookName` | `(name) -> boolean` | Whether a string matches the `use[A-Z]` convention. |
| `useEffectLikeCall` | `(node, additionalHooks?) -> boolean` | Whether a node is a `useEffect`/`useLayoutEffect`-like call. |
| `useStateLikeCall` | `(node, additionalHooks?) -> boolean` | Whether a node is a `useState`-like call. |
| `useEffectSetupCallback` | `(node) -> boolean` | Whether a node is a useEffect setup function. |
| `useEffectCleanupCallback` | `(node) -> boolean` | Whether a node is a useEffect cleanup function. |
Specific hook call predicates (context pre-bound):
| Predicate | Description |
| -------------------------- | ------------------------------------------------ |
| `useActionStateCall` | Whether a node is a `useActionState` call. |
| `useCallbackCall` | Whether a node is a `useCallback` call. |
| `useContextCall` | Whether a node is a `useContext` call. |
| `useDebugValueCall` | Whether a node is a `useDebugValue` call. |
| `useDeferredValueCall` | Whether a node is a `useDeferredValue` call. |
| `useEffectCall` | Whether a node is a `useEffect` call. |
| `useFormStatusCall` | Whether a node is a `useFormStatus` call. |
| `useIdCall` | Whether a node is a `useId` call. |
| `useImperativeHandleCall` | Whether a node is a `useImperativeHandle` call. |
| `useInsertionEffectCall` | Whether a node is a `useInsertionEffect` call. |
| `useLayoutEffectCall` | Whether a node is a `useLayoutEffect` call. |
| `useMemoCall` | Whether a node is a `useMemo` call. |
| `useOptimisticCall` | Whether a node is a `useOptimistic` call. |
| `useReducerCall` | Whether a node is a `useReducer` call. |
| `useRefCall` | Whether a node is a `useRef` call. |
| `useStateCall` | Whether a node is a `useState` call. |
| `useSyncExternalStoreCall` | Whether a node is a `useSyncExternalStore` call. |
| `useTransitionCall` | Whether a node is a `useTransition` call. |
##### React API
Factory functions (context pre-bound):
| Predicate | Signature | Description |
| --------- | -------------------------------- | ---------------------------------------------------------------------------- |
| `API` | `(apiName) -> (node) -> boolean` | Factory: creates a predicate for a React API identifier. (context pre-bound) |
| `APICall` | `(apiName) -> (node) -> boolean` | Factory: creates a predicate for a React API call. (context pre-bound) |
Pre-built call predicates (context pre-bound):
- `captureOwnerStackCall`
- `childrenCountCall`
- `childrenForEachCall`
- `childrenMapCall`
- `childrenOnlyCall`
- `childrenToArrayCall`
- `cloneElementCall`
- `createContextCall`
- `createElementCall`
- `createRefCall`
- `forwardRefCall`
- `lazyCall`
- `memoCall`
- `useCall`
All React API predicates and factories have `context` pre-bound — no need to pass the rule context manually:
```ts
// Direct check
is.memoCall(node);
// Useful in filter/find
nodes.filter(is.memoCall);
// Factory for any API name
const isCreateRefCall = is.APICall("createRef");
isCreateRefCall(node);
```
##### Import source
| Predicate | Signature | Description |
| -------------------- | ----------------------------------------- | ---------------------------------------------------- |
| `APIFromReact` | `(name, scope, importSource?) -> boolean` | Whether a variable comes from a React import. |
| `APIFromReactNative` | `(name, scope, importSource?) -> boolean` | Whether a variable comes from a React Native import. |
---
#### `hint`
Bit-flags that control what the component collector considers a "component". Combine with bitwise OR (`|`) and remove with bitwise AND-NOT (`& ~`).
```ts
// The default hint used when none is specified
hint.component.Default;
// All available flags
hint.component.DoNotIncludeFunctionDefinedAsObjectMethod;
hint.component.DoNotIncludeFunctionDefinedAsClassMethod;
hint.component.DoNotIncludeFunctionDefinedAsArrayMapCallback;
hint.component.DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback;
// … and more (inherits all JsxDetectionHint flags)
```
**Customization example:**
```ts
const { query, visitor } = collect.components(context, {
// Also treat object methods as components (remove the exclusion flag)
hint: hint.component.Default & ~hint.component.DoNotIncludeFunctionDefinedAsObjectMethod,
});
```
---
#### `flag`
Bit-flags indicating component characteristics. Check with bitwise AND (`&`).
```ts
flag.component.None; // 0n — no flags
flag.component.Memo; // wrapped in React.memo
flag.component.ForwardRef; // wrapped in React.forwardRef
```
**Usage:**
```ts
for (const component of query.all(program)) {
if (component.flag & flag.component.Memo) {
// This component is memoized
}
}
```
#### `settings`
Exposes the normalized `react-x` settings from the ESLint shared configuration (`context.settings["react-x"]`). This lets your custom rules read and react to the same project-level settings used by the built-in rules.
| Property | Type | Default | Description |
| ----------------------- | ------------------------------------------------------- | ----------- | --------------------------------------------------------- |
| `version` | `string` | auto-detect | Resolved React version (e.g. `"19.2.4"`). |
| `importSource` | `string` | `"react"` | The module React is imported from (e.g. `"@pika/react"`). |
| `compilationMode` | `"infer" \| "annotation" \| "syntax" \| "all" \| "off"` | `"off"` | The React Compiler compilation mode the project uses. |
| `polymorphicPropName` | `string \| null` | `"as"` | Prop name used for polymorphic components. |
| `additionalStateHooks` | `RegExpLike` | — | Pattern matching custom hooks treated as state hooks. |
| `additionalEffectHooks` | `RegExpLike` | — | Pattern matching custom hooks treated as effect hooks. |
`RegExpLike` is an object with a `test(s: string) => boolean` method (same interface as `RegExp`).
**Usage:**
```ts
import type { RuleFunction } from "@eslint-react/kit";
function version(major = "19"): RuleFunction {
return (context, { settings }) => ({
Program(program) {
if (!settings.version.startsWith(`${major}.`)) {
context.report({
node: program,
message: `This project requires React ${major}, but detected version ${settings.version}.`,
});
}
},
});
}
```
---
## Examples
### Simple: Ban `forwardRef`
This is a simplified kit reimplementation of the built-in [`react-x/no-forwardRef`](https://eslint-react.xyz/docs/rules/no-forward-ref) rule.
```ts
import type { RuleFunction } from "@eslint-react/kit";
function noForwardRef(): RuleFunction {
return (context, { is }) => ({
CallExpression(node) {
if (is.forwardRefCall(node)) {
context.report({ node, message: "forwardRef is deprecated in React 19. Pass ref as a prop instead." });
}
},
});
}
// Usage
eslintReactKit()
.use(noForwardRef)
.getConfig();
```
### Component: Destructure component props
This is a simplified kit reimplementation of the built-in [`react-x/prefer-destructuring-assignment`](https://eslint-react.xyz/docs/rules/prefer-destructuring-assignment) rule.
```ts
import type { RuleFunction } from "@eslint-react/kit";
import { merge } from "@eslint-react/kit";
function destructureComponentProps(): RuleFunction {
return (context, { collect }) => {
const { query, visitor } = collect.components(context);
return merge(visitor, {
"Program:exit"(program) {
for (const { node } of query.all(program)) {
const [props] = node.params;
if (props == null) continue;
if (props.type !== "Identifier") continue;
const propName = props.name;
const propVariable = context.sourceCode.getScope(node).variables.find((v) => v.name === propName);
const propReferences = propVariable?.references ?? [];
for (const ref of propReferences) {
const { parent } = ref.identifier;
if (parent.type !== "MemberExpression") continue;
context.report({
message: "Use destructuring assignment for component props.",
node: parent,
});
}
}
},
});
};
}
// Usage
eslintReactKit()
.use(destructureComponentProps)
.getConfig();
```
### Hooks: Warn on custom hooks that don't call other hooks
This is a simplified kit reimplementation of the built-in [`react-x/no-unnecessary-use-prefix`](https://eslint-react.xyz/docs/rules/no-unnecessary-use-prefix) rule.
```ts
import type { RuleFunction } from "@eslint-react/kit";
import { merge } from "@eslint-react/kit";
function noUnnecessaryUsePrefix(): RuleFunction {
return (context, { collect }) => {
const { query, visitor } = collect.hooks(context);
return merge(visitor, {
"Program:exit"(program) {
for (const { node, hookCalls } of query.all(program)) {
if (hookCalls.length === 0) {
context.report({
node,
message: "A custom hook should use at least one hook, otherwise it's just a regular function.",
});
}
}
},
});
};
}
// Usage
eslintReactKit()
.use(noUnnecessaryUsePrefix)
.getConfig();
```
### Multiple Collectors: No component/hook factories
Disallow defining components or hooks inside other functions (factory pattern).
This is a simplified kit reimplementation of the built-in [`react-x/component-hook-factories`](https://eslint-react.xyz/docs/rules/component-hook-factories) rule.
```ts
import type { RuleFunction } from "@eslint-react/kit";
import { merge } from "@eslint-react/kit";
import type { TSESTree } from "@typescript-eslint/types";
function componentHookFactories(): RuleFunction {
function findParent({ parent }: TSESTree.Node, test: (n: TSESTree.Node) => boolean): TSESTree.Node | null {
if (parent == null) return null;
if (test(parent)) return parent;
if (parent.type === "Program") return null;
return findParent(parent, test);
}
function isFunction({ type }: TSESTree.Node) {
return type === "FunctionDeclaration" || type === "FunctionExpression" || type === "ArrowFunctionExpression";
}
return (context, { collect }) => {
const fc = collect.components(context);
const hk = collect.hooks(context);
return merge(
fc.visitor,
hk.visitor,
{
"Program:exit"(program) {
const comps = fc.query.all(program);
const hooks = hk.query.all(program);
for (const { name, node, kind } of [...comps, ...hooks]) {
if (name == null) continue;
if (findParent(node, isFunction) == null) continue;
context.report({
node,
message: `Don't define ${kind} "${name}" inside a function. Move it to the module level.`,
});
}
},
},
);
};
}
// Usage
eslintReactKit()
.use(componentHookFactories)
.getConfig();
```
### Override Config: Using spread syntax with `getConfig`
`getConfig()` returns a plain config object. You can spread it into a new object to override or supplement its properties — for example, to scope the config to specific files or add extra settings alongside your kit rules.
```ts
import eslintReactKit from "@eslint-react/kit";
import type { RuleFunction } from "@eslint-react/kit";
// Spread the config into a new object to add or override properties like `files`:
export default [
{
...eslintReactKit()
.use(noForwardRef)
.use(version, "19")
.getConfig(),
// Override `name` so it shows your own label in config inspector tools
name: "react-custom-rules",
// Override `files` to scope the kit rules to specific source files
files: ["src/**/*.ts", "src/**/*.tsx"],
},
];
```
This pattern is especially useful when composing kit configs alongside other ESLint configs (e.g. `typescript-eslint`, `eslint-plugin-react-hooks`) via `defineConfig` — you can nest the spread object inside `extends` arrays and attach shared properties like `files` or `languageOptions` at the same level.
### Advanced Config: Using `getPlugin` for custom plugin namespace
Use `getPlugin()` when you want full control over the plugin namespace and rule severities instead of the all-in-one `getConfig()`.
```ts
import eslintReactKit from "@eslint-react/kit";
import type { RuleFunction } from "@eslint-react/kit";
const kit = eslintReactKit()
.use(noForwardRef)
.use(version, "19");
// Instead of kit.getConfig(), use kit.getPlugin() for full control:
const plugin = kit.getPlugin();
export default [
{
files: ["**/*.{ts,tsx}"],
plugins: {
// Choose your own namespace
"react-custom-rules": plugin,
},
rules: {
// Set individual severities
"react-custom-rules/no-forward-ref": "error",
"react-custom-rules/version": "error",
},
},
];
```
## More Examples
Please check the [Rule Recipes](https://www.eslint-react.xyz/docs/recipes) in the documentation site.
For full documentation, see [https://beta.eslint-react.xyz/docs/packages/kit](https://beta.eslint-react.xyz/docs/packages/kit).