@eslint-react/kit 5.2.4-beta.2 → 5.2.4-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -656
- package/dist/index.js +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,658 +1,3 @@
|
|
|
1
1
|
# @eslint-react/kit
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Index
|
|
6
|
-
|
|
7
|
-
- [Installation](#installation)
|
|
8
|
-
- [Quick Start](#quick-start)
|
|
9
|
-
- [API Reference](#api-reference)
|
|
10
|
-
- [`eslintReactKit` (default export)](#eslintreactkit-default-export)
|
|
11
|
-
- [`RuleFunction`](#rulefunction)
|
|
12
|
-
- [`Builder`](#builder)
|
|
13
|
-
- [`getConfig`](#getconfig)
|
|
14
|
-
- [`getPlugin`](#getplugin)
|
|
15
|
-
- [`merge`](#merge)
|
|
16
|
-
- [`RuleToolkit` — the toolkit object](#ruletoolkit--the-toolkit-object)
|
|
17
|
-
- [`collect`](#collect) — Semantic collectors
|
|
18
|
-
- [`is`](#is) — Predicates
|
|
19
|
-
- [`hint`](#hint) — Detection hint bit-flags
|
|
20
|
-
- [`flag`](#flag) — Component characteristic flags
|
|
21
|
-
- [`settings`](#settings) — Normalized ESLint React settings
|
|
22
|
-
- [Examples](#examples)
|
|
23
|
-
- [Simple: Ban `forwardRef`](#simple-ban-forwardref)
|
|
24
|
-
- [Component: Destructure component props](#component-destructure-component-props)
|
|
25
|
-
- [Hooks: Warn on custom hooks that don't call other hooks](#hooks-warn-on-custom-hooks-that-dont-call-other-hooks)
|
|
26
|
-
- [Multiple Collectors: No component/hook factories](#multiple-collectors-no-componenthook-factories)
|
|
27
|
-
- [Override Config: Using spread syntax with `getConfig`](#override-config-using-spread-syntax-with-getconfig)
|
|
28
|
-
- [Advanced Config: Using `getPlugin` for custom plugin namespace](#advanced-config-using-getplugin-for-custom-plugin-namespace)
|
|
29
|
-
- [More Examples](#more-examples)
|
|
30
|
-
|
|
31
|
-
## Installation
|
|
32
|
-
|
|
33
|
-
```sh
|
|
34
|
-
npm install --save-dev @eslint-react/kit
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## Quick Start
|
|
38
|
-
|
|
39
|
-
```ts
|
|
40
|
-
import eslintReact from "@eslint-react/eslint-plugin";
|
|
41
|
-
import eslintReactKit, { merge } from "@eslint-react/kit";
|
|
42
|
-
import type { RuleFunction } from "@eslint-react/kit";
|
|
43
|
-
import eslintJs from "@eslint/js";
|
|
44
|
-
import { defineConfig } from "eslint/config";
|
|
45
|
-
import tseslint from "typescript-eslint";
|
|
46
|
-
|
|
47
|
-
/** Enforce function declarations for function components. */
|
|
48
|
-
function functionComponentDefinition(): RuleFunction {
|
|
49
|
-
return (context, { collect }) => {
|
|
50
|
-
const { query, visitor } = collect.components(context);
|
|
51
|
-
return merge(
|
|
52
|
-
visitor,
|
|
53
|
-
{
|
|
54
|
-
"Program:exit"(program) {
|
|
55
|
-
for (const { node } of query.all(program)) {
|
|
56
|
-
if (node.type === "FunctionDeclaration") continue;
|
|
57
|
-
context.report({
|
|
58
|
-
node,
|
|
59
|
-
message: "Function components must be defined with function declarations.",
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
);
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export default defineConfig(
|
|
69
|
-
{
|
|
70
|
-
files: ["**/*.{ts,tsx}"],
|
|
71
|
-
extends: [
|
|
72
|
-
eslintJs.configs.recommended,
|
|
73
|
-
tseslint.configs.recommended,
|
|
74
|
-
eslintReact.configs["recommended-typescript"],
|
|
75
|
-
eslintReactKit()
|
|
76
|
-
.use(functionComponentDefinition)
|
|
77
|
-
.getConfig(),
|
|
78
|
-
],
|
|
79
|
-
},
|
|
80
|
-
);
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
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.
|
|
84
|
-
|
|
85
|
-
## API Reference
|
|
86
|
-
|
|
87
|
-
### `eslintReactKit` (default export)
|
|
88
|
-
|
|
89
|
-
```ts
|
|
90
|
-
import eslintReactKit from "@eslint-react/kit";
|
|
91
|
-
|
|
92
|
-
eslintReactKit(): Builder
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Creates a `Builder` instance for registering custom rules via the chainable `.use()` API.
|
|
96
|
-
|
|
97
|
-
### `RuleFunction`
|
|
98
|
-
|
|
99
|
-
```ts
|
|
100
|
-
import type { RuleFunction } from "@eslint-react/kit";
|
|
101
|
-
|
|
102
|
-
type RuleFunction = (context: RuleContext, toolkit: RuleToolkit) => RuleListener;
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
A function that receives the ESLint rule context and the structured `RuleToolkit` toolkit, and returns a `RuleListener` (AST visitor object).
|
|
106
|
-
|
|
107
|
-
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.
|
|
108
|
-
|
|
109
|
-
```ts
|
|
110
|
-
// Function name `noForwardRef` → rule name `no-forward-ref`
|
|
111
|
-
// Registered as `@eslint-react/kit/no-forward-ref`
|
|
112
|
-
function noForwardRef(): RuleFunction {
|
|
113
|
-
return (context, { is }) => ({ ... });
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Functions that accept options work the same way
|
|
117
|
-
function forbidElements({ forbidden }: ForbidElementsOptions): RuleFunction {
|
|
118
|
-
return (context) => ({ ... });
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
#### Anonymous Rules
|
|
123
|
-
|
|
124
|
-
When you use an **anonymous function** (arrow function without a name) with `.use()`, a random ULID is automatically generated as the rule name:
|
|
125
|
-
|
|
126
|
-
```ts
|
|
127
|
-
// Anonymous rule → random ULID name like "01KNE2WSJ8011D2HXE3A6H717C"
|
|
128
|
-
// Registered as `@eslint-react/kit/01KNE2WSJ8011D2HXE3A6H717C`
|
|
129
|
-
eslintReactKit().use(() => (context) => ({
|
|
130
|
-
JSXOpeningElement(node) {
|
|
131
|
-
// Critical check that cannot be easily disabled
|
|
132
|
-
},
|
|
133
|
-
}));
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
> **Note:** The rule names are ULIDs generated randomly on each ESLint run. The examples above illustrate the format — actual values will differ every time.
|
|
137
|
-
|
|
138
|
-
Anonymous rules are ideal for checks that are **critical to code quality or security** and should never be bypassed via disable comments:
|
|
139
|
-
|
|
140
|
-
```ts
|
|
141
|
-
// This critical security check cannot be easily disabled by developers
|
|
142
|
-
eslintReactKit().use(() => (context, { is }) => ({
|
|
143
|
-
CallExpression(node) {
|
|
144
|
-
// Prevent dangerous API calls that could lead to XSS
|
|
145
|
-
if (is.createElementCall(node) && isUnsafeArguments(node.arguments)) {
|
|
146
|
-
context.report({
|
|
147
|
-
node,
|
|
148
|
-
message: "Potential XSS vulnerability detected. This issue must be fixed.",
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
},
|
|
152
|
-
}));
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
**Note:** Since the rule name is random and changes on every ESLint run, developers cannot use standard rules configs or disable comments like:
|
|
156
|
-
|
|
157
|
-
```ts
|
|
158
|
-
// This will NOT work - the rule name is random!
|
|
159
|
-
{ rules: { "@eslint-react/kit/01KNE2WSJ8011D2HXE3A6H717C": "off" } }
|
|
160
|
-
|
|
161
|
-
// This will NOT work - the rule name is random!
|
|
162
|
-
// eslint-disable-next-line @eslint-react/kit/01KNE2WSJ8011D2HXE3A6H717C
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
To disable an anonymous rule, developers must modify the ESLint configuration file directly, which provides an audit trail for policy violations.
|
|
166
|
-
|
|
167
|
-
### `Builder`
|
|
168
|
-
|
|
169
|
-
```ts
|
|
170
|
-
interface Builder {
|
|
171
|
-
use<F extends (...args: any[]) => RuleFunction>(factory: F, ...args: Parameters<F>): Builder;
|
|
172
|
-
getConfig(): Linter.Config;
|
|
173
|
-
getPlugin(): ESLint.Plugin;
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
A chainable builder for registering custom rules.
|
|
178
|
-
|
|
179
|
-
| Method | Description |
|
|
180
|
-
| ----------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
181
|
-
| `use` | Registers a rule factory. The rule name is `kebabCase(factory.name)`. Options type is inferred from the factory signature. |
|
|
182
|
-
| `getConfig` | Returns a `Linter.Config` with all registered rules enabled at `"error"` severity. |
|
|
183
|
-
| `getPlugin` | Returns an `ESLint.Plugin` containing the registered rules and plugin metadata. |
|
|
184
|
-
|
|
185
|
-
#### `getConfig`
|
|
186
|
-
|
|
187
|
-
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.
|
|
188
|
-
|
|
189
|
-
```ts
|
|
190
|
-
eslintReactKit()
|
|
191
|
-
.use(noForwardRef) // no-arg factory
|
|
192
|
-
.use(version, "19") // factory with inferred options
|
|
193
|
-
.getConfig();
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
#### `getPlugin`
|
|
197
|
-
|
|
198
|
-
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.
|
|
199
|
-
|
|
200
|
-
```ts
|
|
201
|
-
const kit = eslintReactKit()
|
|
202
|
-
.use(noForwardRef)
|
|
203
|
-
.use(version, "19");
|
|
204
|
-
|
|
205
|
-
// Retrieve the raw plugin object
|
|
206
|
-
const plugin = kit.getPlugin();
|
|
207
|
-
|
|
208
|
-
// Use it in a custom flat config with your own namespace and severity
|
|
209
|
-
export default [
|
|
210
|
-
{
|
|
211
|
-
files: ["**/*.{ts,tsx}"],
|
|
212
|
-
plugins: {
|
|
213
|
-
react: plugin,
|
|
214
|
-
},
|
|
215
|
-
rules: {
|
|
216
|
-
"react/version": "error",
|
|
217
|
-
"react/no-forward-ref": "error",
|
|
218
|
-
},
|
|
219
|
-
},
|
|
220
|
-
];
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
### `merge`
|
|
224
|
-
|
|
225
|
-
```ts
|
|
226
|
-
import { merge } from "@eslint-react/kit";
|
|
227
|
-
|
|
228
|
-
merge(...listeners: RuleListener[]): RuleListener
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
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.
|
|
232
|
-
|
|
233
|
-
This is essential for combining a collector's `visitor` with your own inspection logic.
|
|
234
|
-
|
|
235
|
-
### `RuleToolkit` — the toolkit object
|
|
236
|
-
|
|
237
|
-
The second argument passed to the `RuleFunction` function is a structured `RuleToolkit` object:
|
|
238
|
-
|
|
239
|
-
```
|
|
240
|
-
kit
|
|
241
|
-
├── collect -> Semantic collectors (components, hooks)
|
|
242
|
-
├── is -> All predicates (component, hook, React API, import source)
|
|
243
|
-
├── hint -> Detection hint bit-flags
|
|
244
|
-
├── flag -> Component characteristic bit-flags
|
|
245
|
-
├── settings -> Normalized ESLint React settings
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
---
|
|
249
|
-
|
|
250
|
-
#### `collect`
|
|
251
|
-
|
|
252
|
-
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.
|
|
253
|
-
|
|
254
|
-
| Method | Returns | Description |
|
|
255
|
-
| ------------------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------- |
|
|
256
|
-
| `components(context, options?)` | `CollectorWithContext<FunctionComponentSemanticNode>` | Detects function components. Options: `{ hint?: bigint, collectDisplayName?: boolean }` |
|
|
257
|
-
| `hooks(context)` | `CollectorWithContext<HookSemanticNode>` | Detects custom hook definitions. |
|
|
258
|
-
|
|
259
|
-
**`CollectorWithContext`** extends `Collector` with contextual queries:
|
|
260
|
-
|
|
261
|
-
| Query | Description |
|
|
262
|
-
| -------------------- | ----------------------------------------- |
|
|
263
|
-
| `query.all(program)` | All collected semantic nodes in the file. |
|
|
264
|
-
|
|
265
|
-
---
|
|
266
|
-
|
|
267
|
-
#### `is`
|
|
268
|
-
|
|
269
|
-
All predicates live under `kit.is` — organized into four sub-sections.
|
|
270
|
-
|
|
271
|
-
##### Component
|
|
272
|
-
|
|
273
|
-
| Predicate | Signature | Description |
|
|
274
|
-
| -------------------------- | ------------------------- | --------------------------------------------------------------------------- |
|
|
275
|
-
| `componentDecl` | `(node, hint) -> boolean` | Whether a function node is a component. (context pre-bound) |
|
|
276
|
-
| `componentName` | `(name) -> boolean` | Strict PascalCase component name check. |
|
|
277
|
-
| `componentNameLoose` | `(name) -> boolean` | Loose component name check. |
|
|
278
|
-
| `componentWrapperCall` | `(node) -> boolean` | Whether a node is a `memo(…)` or `forwardRef(…)` call. (context pre-bound) |
|
|
279
|
-
| `componentWrapperCallback` | `(node) -> boolean` | Whether a function is the callback passed to a wrapper. (context pre-bound) |
|
|
280
|
-
|
|
281
|
-
##### Hook
|
|
282
|
-
|
|
283
|
-
General hook predicates:
|
|
284
|
-
|
|
285
|
-
| Predicate | Signature | Description |
|
|
286
|
-
| -------------------------- | ------------------------------------- | ------------------------------------------------------------ |
|
|
287
|
-
| `hookDecl` | `(node) -> boolean` | Whether a function node is a hook (by name). |
|
|
288
|
-
| `hookCall` | `(node) -> boolean` | Whether a node is a hook call. |
|
|
289
|
-
| `hookName` | `(name) -> boolean` | Whether a string matches the `use[A-Z]` convention. |
|
|
290
|
-
| `useEffectLikeCall` | `(node, additionalHooks?) -> boolean` | Whether a node is a `useEffect`/`useLayoutEffect`-like call. |
|
|
291
|
-
| `useStateLikeCall` | `(node, additionalHooks?) -> boolean` | Whether a node is a `useState`-like call. |
|
|
292
|
-
| `useEffectSetupCallback` | `(node) -> boolean` | Whether a node is a useEffect setup function. |
|
|
293
|
-
| `useEffectCleanupCallback` | `(node) -> boolean` | Whether a node is a useEffect cleanup function. |
|
|
294
|
-
|
|
295
|
-
Specific hook call predicates (context pre-bound):
|
|
296
|
-
|
|
297
|
-
| Predicate | Description |
|
|
298
|
-
| -------------------------- | ------------------------------------------------ |
|
|
299
|
-
| `useActionStateCall` | Whether a node is a `useActionState` call. |
|
|
300
|
-
| `useCallbackCall` | Whether a node is a `useCallback` call. |
|
|
301
|
-
| `useContextCall` | Whether a node is a `useContext` call. |
|
|
302
|
-
| `useDebugValueCall` | Whether a node is a `useDebugValue` call. |
|
|
303
|
-
| `useDeferredValueCall` | Whether a node is a `useDeferredValue` call. |
|
|
304
|
-
| `useEffectCall` | Whether a node is a `useEffect` call. |
|
|
305
|
-
| `useFormStatusCall` | Whether a node is a `useFormStatus` call. |
|
|
306
|
-
| `useIdCall` | Whether a node is a `useId` call. |
|
|
307
|
-
| `useImperativeHandleCall` | Whether a node is a `useImperativeHandle` call. |
|
|
308
|
-
| `useInsertionEffectCall` | Whether a node is a `useInsertionEffect` call. |
|
|
309
|
-
| `useLayoutEffectCall` | Whether a node is a `useLayoutEffect` call. |
|
|
310
|
-
| `useMemoCall` | Whether a node is a `useMemo` call. |
|
|
311
|
-
| `useOptimisticCall` | Whether a node is a `useOptimistic` call. |
|
|
312
|
-
| `useReducerCall` | Whether a node is a `useReducer` call. |
|
|
313
|
-
| `useRefCall` | Whether a node is a `useRef` call. |
|
|
314
|
-
| `useStateCall` | Whether a node is a `useState` call. |
|
|
315
|
-
| `useSyncExternalStoreCall` | Whether a node is a `useSyncExternalStore` call. |
|
|
316
|
-
| `useTransitionCall` | Whether a node is a `useTransition` call. |
|
|
317
|
-
|
|
318
|
-
##### React API
|
|
319
|
-
|
|
320
|
-
Factory functions (context pre-bound):
|
|
321
|
-
|
|
322
|
-
| Predicate | Signature | Description |
|
|
323
|
-
| --------- | -------------------------------- | ---------------------------------------------------------------------------- |
|
|
324
|
-
| `API` | `(apiName) -> (node) -> boolean` | Factory: creates a predicate for a React API identifier. (context pre-bound) |
|
|
325
|
-
| `APICall` | `(apiName) -> (node) -> boolean` | Factory: creates a predicate for a React API call. (context pre-bound) |
|
|
326
|
-
|
|
327
|
-
Pre-built call predicates (context pre-bound):
|
|
328
|
-
|
|
329
|
-
- `captureOwnerStackCall`
|
|
330
|
-
- `childrenCountCall`
|
|
331
|
-
- `childrenForEachCall`
|
|
332
|
-
- `childrenMapCall`
|
|
333
|
-
- `childrenOnlyCall`
|
|
334
|
-
- `childrenToArrayCall`
|
|
335
|
-
- `cloneElementCall`
|
|
336
|
-
- `createContextCall`
|
|
337
|
-
- `createElementCall`
|
|
338
|
-
- `createRefCall`
|
|
339
|
-
- `forwardRefCall`
|
|
340
|
-
- `lazyCall`
|
|
341
|
-
- `memoCall`
|
|
342
|
-
- `useCall`
|
|
343
|
-
|
|
344
|
-
All React API predicates and factories have `context` pre-bound — no need to pass the rule context manually:
|
|
345
|
-
|
|
346
|
-
```ts
|
|
347
|
-
// Direct check
|
|
348
|
-
is.memoCall(node);
|
|
349
|
-
|
|
350
|
-
// Useful in filter/find
|
|
351
|
-
nodes.filter(is.memoCall);
|
|
352
|
-
|
|
353
|
-
// Factory for any API name
|
|
354
|
-
const isCreateRefCall = is.APICall("createRef");
|
|
355
|
-
isCreateRefCall(node);
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
##### Import source
|
|
359
|
-
|
|
360
|
-
| Predicate | Signature | Description |
|
|
361
|
-
| -------------------- | ----------------------------------------- | ---------------------------------------------------- |
|
|
362
|
-
| `APIFromReact` | `(name, scope, importSource?) -> boolean` | Whether a variable comes from a React import. |
|
|
363
|
-
| `APIFromReactNative` | `(name, scope, importSource?) -> boolean` | Whether a variable comes from a React Native import. |
|
|
364
|
-
|
|
365
|
-
---
|
|
366
|
-
|
|
367
|
-
#### `hint`
|
|
368
|
-
|
|
369
|
-
Bit-flags that control what the component collector considers a "component". Combine with bitwise OR (`|`) and remove with bitwise AND-NOT (`& ~`).
|
|
370
|
-
|
|
371
|
-
```ts
|
|
372
|
-
// The default hint used when none is specified
|
|
373
|
-
hint.component.Default;
|
|
374
|
-
|
|
375
|
-
// All available flags
|
|
376
|
-
hint.component.DoNotIncludeFunctionDefinedAsObjectMethod;
|
|
377
|
-
hint.component.DoNotIncludeFunctionDefinedAsClassMethod;
|
|
378
|
-
hint.component.DoNotIncludeFunctionDefinedAsArrayMapCallback;
|
|
379
|
-
hint.component.DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback;
|
|
380
|
-
// … and more (inherits all JsxDetectionHint flags)
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
**Customization example:**
|
|
384
|
-
|
|
385
|
-
```ts
|
|
386
|
-
const { query, visitor } = collect.components(context, {
|
|
387
|
-
// Also treat object methods as components (remove the exclusion flag)
|
|
388
|
-
hint: hint.component.Default & ~hint.component.DoNotIncludeFunctionDefinedAsObjectMethod,
|
|
389
|
-
});
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
---
|
|
393
|
-
|
|
394
|
-
#### `flag`
|
|
395
|
-
|
|
396
|
-
Bit-flags indicating component characteristics. Check with bitwise AND (`&`).
|
|
397
|
-
|
|
398
|
-
```ts
|
|
399
|
-
flag.component.None; // 0n — no flags
|
|
400
|
-
flag.component.Memo; // wrapped in React.memo
|
|
401
|
-
flag.component.ForwardRef; // wrapped in React.forwardRef
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
**Usage:**
|
|
405
|
-
|
|
406
|
-
```ts
|
|
407
|
-
for (const component of query.all(program)) {
|
|
408
|
-
if (component.flag & flag.component.Memo) {
|
|
409
|
-
// This component is memoized
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
#### `settings`
|
|
415
|
-
|
|
416
|
-
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.
|
|
417
|
-
|
|
418
|
-
| Property | Type | Default | Description |
|
|
419
|
-
| ----------------------- | ------------------------------------------------------- | ----------- | --------------------------------------------------------- |
|
|
420
|
-
| `version` | `string` | auto-detect | Resolved React version (e.g. `"19.2.4"`). |
|
|
421
|
-
| `importSource` | `string` | `"react"` | The module React is imported from (e.g. `"@pika/react"`). |
|
|
422
|
-
| `compilationMode` | `"infer" \| "annotation" \| "syntax" \| "all" \| "off"` | `"off"` | The React Compiler compilation mode the project uses. |
|
|
423
|
-
| `polymorphicPropName` | `string \| null` | `"as"` | Prop name used for polymorphic components. |
|
|
424
|
-
| `additionalStateHooks` | `RegExpLike` | — | Pattern matching custom hooks treated as state hooks. |
|
|
425
|
-
| `additionalEffectHooks` | `RegExpLike` | — | Pattern matching custom hooks treated as effect hooks. |
|
|
426
|
-
|
|
427
|
-
`RegExpLike` is an object with a `test(s: string) => boolean` method (same interface as `RegExp`).
|
|
428
|
-
|
|
429
|
-
**Usage:**
|
|
430
|
-
|
|
431
|
-
```ts
|
|
432
|
-
import type { RuleFunction } from "@eslint-react/kit";
|
|
433
|
-
|
|
434
|
-
function version(major = "19"): RuleFunction {
|
|
435
|
-
return (context, { settings }) => ({
|
|
436
|
-
Program(program) {
|
|
437
|
-
if (!settings.version.startsWith(`${major}.`)) {
|
|
438
|
-
context.report({
|
|
439
|
-
node: program,
|
|
440
|
-
message: `This project requires React ${major}, but detected version ${settings.version}.`,
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
},
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
---
|
|
449
|
-
|
|
450
|
-
## Examples
|
|
451
|
-
|
|
452
|
-
### Simple: Ban `forwardRef`
|
|
453
|
-
|
|
454
|
-
This is a simplified kit reimplementation of the built-in [`react-x/no-forwardRef`](https://eslint-react.xyz/docs/rules/no-forward-ref) rule.
|
|
455
|
-
|
|
456
|
-
```ts
|
|
457
|
-
import type { RuleFunction } from "@eslint-react/kit";
|
|
458
|
-
|
|
459
|
-
function noForwardRef(): RuleFunction {
|
|
460
|
-
return (context, { is }) => ({
|
|
461
|
-
CallExpression(node) {
|
|
462
|
-
if (is.forwardRefCall(node)) {
|
|
463
|
-
context.report({ node, message: "forwardRef is deprecated in React 19. Pass ref as a prop instead." });
|
|
464
|
-
}
|
|
465
|
-
},
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// Usage
|
|
470
|
-
eslintReactKit()
|
|
471
|
-
.use(noForwardRef)
|
|
472
|
-
.getConfig();
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
### Component: Destructure component props
|
|
476
|
-
|
|
477
|
-
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.
|
|
478
|
-
|
|
479
|
-
```ts
|
|
480
|
-
import type { RuleFunction } from "@eslint-react/kit";
|
|
481
|
-
import { merge } from "@eslint-react/kit";
|
|
482
|
-
|
|
483
|
-
function destructureComponentProps(): RuleFunction {
|
|
484
|
-
return (context, { collect }) => {
|
|
485
|
-
const { query, visitor } = collect.components(context);
|
|
486
|
-
|
|
487
|
-
return merge(visitor, {
|
|
488
|
-
"Program:exit"(program) {
|
|
489
|
-
for (const { node } of query.all(program)) {
|
|
490
|
-
const [props] = node.params;
|
|
491
|
-
if (props == null) continue;
|
|
492
|
-
if (props.type !== "Identifier") continue;
|
|
493
|
-
const propName = props.name;
|
|
494
|
-
const propVariable = context.sourceCode.getScope(node).variables.find((v) => v.name === propName);
|
|
495
|
-
const propReferences = propVariable?.references ?? [];
|
|
496
|
-
for (const ref of propReferences) {
|
|
497
|
-
const { parent } = ref.identifier;
|
|
498
|
-
if (parent.type !== "MemberExpression") continue;
|
|
499
|
-
context.report({
|
|
500
|
-
message: "Use destructuring assignment for component props.",
|
|
501
|
-
node: parent,
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
},
|
|
506
|
-
});
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// Usage
|
|
511
|
-
eslintReactKit()
|
|
512
|
-
.use(destructureComponentProps)
|
|
513
|
-
.getConfig();
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
### Hooks: Warn on custom hooks that don't call other hooks
|
|
517
|
-
|
|
518
|
-
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.
|
|
519
|
-
|
|
520
|
-
```ts
|
|
521
|
-
import type { RuleFunction } from "@eslint-react/kit";
|
|
522
|
-
import { merge } from "@eslint-react/kit";
|
|
523
|
-
|
|
524
|
-
function noUnnecessaryUsePrefix(): RuleFunction {
|
|
525
|
-
return (context, { collect }) => {
|
|
526
|
-
const { query, visitor } = collect.hooks(context);
|
|
527
|
-
|
|
528
|
-
return merge(visitor, {
|
|
529
|
-
"Program:exit"(program) {
|
|
530
|
-
for (const { node, hookCalls } of query.all(program)) {
|
|
531
|
-
if (hookCalls.length === 0) {
|
|
532
|
-
context.report({
|
|
533
|
-
node,
|
|
534
|
-
message: "A custom hook should use at least one hook, otherwise it's just a regular function.",
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
},
|
|
539
|
-
});
|
|
540
|
-
};
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// Usage
|
|
544
|
-
eslintReactKit()
|
|
545
|
-
.use(noUnnecessaryUsePrefix)
|
|
546
|
-
.getConfig();
|
|
547
|
-
```
|
|
548
|
-
|
|
549
|
-
### Multiple Collectors: No component/hook factories
|
|
550
|
-
|
|
551
|
-
Disallow defining components or hooks inside other functions (factory pattern).
|
|
552
|
-
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.
|
|
553
|
-
|
|
554
|
-
```ts
|
|
555
|
-
import type { RuleFunction } from "@eslint-react/kit";
|
|
556
|
-
import { merge } from "@eslint-react/kit";
|
|
557
|
-
import type { TSESTree } from "@typescript-eslint/types";
|
|
558
|
-
|
|
559
|
-
function componentHookFactories(): RuleFunction {
|
|
560
|
-
function findParent({ parent }: TSESTree.Node, test: (n: TSESTree.Node) => boolean): TSESTree.Node | null {
|
|
561
|
-
if (parent == null) return null;
|
|
562
|
-
if (test(parent)) return parent;
|
|
563
|
-
if (parent.type === "Program") return null;
|
|
564
|
-
return findParent(parent, test);
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
function isFunction({ type }: TSESTree.Node) {
|
|
568
|
-
return type === "FunctionDeclaration" || type === "FunctionExpression" || type === "ArrowFunctionExpression";
|
|
569
|
-
}
|
|
570
|
-
return (context, { collect }) => {
|
|
571
|
-
const fc = collect.components(context);
|
|
572
|
-
const hk = collect.hooks(context);
|
|
573
|
-
return merge(
|
|
574
|
-
fc.visitor,
|
|
575
|
-
hk.visitor,
|
|
576
|
-
{
|
|
577
|
-
"Program:exit"(program) {
|
|
578
|
-
const comps = fc.query.all(program);
|
|
579
|
-
const hooks = hk.query.all(program);
|
|
580
|
-
for (const { name, node, kind } of [...comps, ...hooks]) {
|
|
581
|
-
if (name == null) continue;
|
|
582
|
-
if (findParent(node, isFunction) == null) continue;
|
|
583
|
-
context.report({
|
|
584
|
-
node,
|
|
585
|
-
message: `Don't define ${kind} "${name}" inside a function. Move it to the module level.`,
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
},
|
|
589
|
-
},
|
|
590
|
-
);
|
|
591
|
-
};
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// Usage
|
|
595
|
-
eslintReactKit()
|
|
596
|
-
.use(componentHookFactories)
|
|
597
|
-
.getConfig();
|
|
598
|
-
```
|
|
599
|
-
|
|
600
|
-
### Override Config: Using spread syntax with `getConfig`
|
|
601
|
-
|
|
602
|
-
`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.
|
|
603
|
-
|
|
604
|
-
```ts
|
|
605
|
-
import eslintReactKit from "@eslint-react/kit";
|
|
606
|
-
import type { RuleFunction } from "@eslint-react/kit";
|
|
607
|
-
|
|
608
|
-
// Spread the config into a new object to add or override properties like `files`:
|
|
609
|
-
export default [
|
|
610
|
-
{
|
|
611
|
-
...eslintReactKit()
|
|
612
|
-
.use(noForwardRef)
|
|
613
|
-
.use(version, "19")
|
|
614
|
-
.getConfig(),
|
|
615
|
-
// Override `name` so it shows your own label in config inspector tools
|
|
616
|
-
name: "react-custom-rules",
|
|
617
|
-
// Override `files` to scope the kit rules to specific source files
|
|
618
|
-
files: ["src/**/*.ts", "src/**/*.tsx"],
|
|
619
|
-
},
|
|
620
|
-
];
|
|
621
|
-
```
|
|
622
|
-
|
|
623
|
-
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.
|
|
624
|
-
|
|
625
|
-
### Advanced Config: Using `getPlugin` for custom plugin namespace
|
|
626
|
-
|
|
627
|
-
Use `getPlugin()` when you want full control over the plugin namespace and rule severities instead of the all-in-one `getConfig()`.
|
|
628
|
-
|
|
629
|
-
```ts
|
|
630
|
-
import eslintReactKit from "@eslint-react/kit";
|
|
631
|
-
import type { RuleFunction } from "@eslint-react/kit";
|
|
632
|
-
|
|
633
|
-
const kit = eslintReactKit()
|
|
634
|
-
.use(noForwardRef)
|
|
635
|
-
.use(version, "19");
|
|
636
|
-
|
|
637
|
-
// Instead of kit.getConfig(), use kit.getPlugin() for full control:
|
|
638
|
-
const plugin = kit.getPlugin();
|
|
639
|
-
|
|
640
|
-
export default [
|
|
641
|
-
{
|
|
642
|
-
files: ["**/*.{ts,tsx}"],
|
|
643
|
-
plugins: {
|
|
644
|
-
// Choose your own namespace
|
|
645
|
-
"react-custom-rules": plugin,
|
|
646
|
-
},
|
|
647
|
-
rules: {
|
|
648
|
-
// Set individual severities
|
|
649
|
-
"react-custom-rules/no-forward-ref": "error",
|
|
650
|
-
"react-custom-rules/version": "error",
|
|
651
|
-
},
|
|
652
|
-
},
|
|
653
|
-
];
|
|
654
|
-
```
|
|
655
|
-
|
|
656
|
-
## More Examples
|
|
657
|
-
|
|
658
|
-
Please check the [Rule Recipes](https://www.eslint-react.xyz/docs/recipes) in the documentation site.
|
|
3
|
+
For full documentation, see [https://beta.eslint-react.xyz/docs/packages/kit](https://beta.eslint-react.xyz/docs/packages/kit).
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eslint-react/kit",
|
|
3
|
-
"version": "5.2.4-beta.
|
|
3
|
+
"version": "5.2.4-beta.4",
|
|
4
4
|
"description": "ESLint React's utility module for building custom React rules with JavaScript functions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -38,16 +38,16 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@typescript-eslint/utils": "^8.58.2",
|
|
40
40
|
"string-ts": "^2.3.1",
|
|
41
|
-
"@eslint-react/core": "5.2.4-beta.
|
|
42
|
-
"@eslint-react/shared": "5.2.4-beta.
|
|
41
|
+
"@eslint-react/core": "5.2.4-beta.4",
|
|
42
|
+
"@eslint-react/shared": "5.2.4-beta.4"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"eslint": "^10.2.0",
|
|
46
46
|
"tsdown": "^0.21.9",
|
|
47
|
-
"@eslint-react/
|
|
48
|
-
"@local/
|
|
49
|
-
"@eslint-react/
|
|
50
|
-
"@local/
|
|
47
|
+
"@eslint-react/eslint": "5.2.4-beta.4",
|
|
48
|
+
"@local/configs": "0.0.0",
|
|
49
|
+
"@eslint-react/ast": "5.2.4-beta.4",
|
|
50
|
+
"@local/eff": "3.0.0-beta.72"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"eslint": "^10.2.0",
|