@eslint-react/kit 4.0.0-beta.2 → 4.0.1-beta.0
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 +136 -77
- package/dist/index.d.ts +63 -63
- package/dist/index.js +104 -97
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -7,7 +7,9 @@ ESLint React's toolkit for building custom React lint rules right inside your `e
|
|
|
7
7
|
- [Installation](#installation)
|
|
8
8
|
- [Quick Start](#quick-start)
|
|
9
9
|
- [API Reference](#api-reference)
|
|
10
|
-
- [`
|
|
10
|
+
- [`eslintReactKit` (default export)](#eslintreactkit-default-export)
|
|
11
|
+
- [`RuleDefinition`](#ruledefinition)
|
|
12
|
+
- [`KitBuilder`](#kitbuilder)
|
|
11
13
|
- [`merge`](#merge)
|
|
12
14
|
- [`Kit` — the toolkit object](#kit--the-toolkit-object)
|
|
13
15
|
- [`kit.collect`](#kitcollect) — Semantic collectors
|
|
@@ -25,7 +27,7 @@ ESLint React's toolkit for building custom React lint rules right inside your `e
|
|
|
25
27
|
## Installation
|
|
26
28
|
|
|
27
29
|
```sh
|
|
28
|
-
npm install --save-dev @eslint-react/kit
|
|
30
|
+
npm install --save-dev @eslint-react/kit@beta
|
|
29
31
|
```
|
|
30
32
|
|
|
31
33
|
## Quick Start
|
|
@@ -33,10 +35,32 @@ npm install --save-dev @eslint-react/kit
|
|
|
33
35
|
```ts
|
|
34
36
|
import eslintReact from "@eslint-react/eslint-plugin";
|
|
35
37
|
import eslintReactKit, { merge } from "@eslint-react/kit";
|
|
38
|
+
import type { RuleDefinition } from "@eslint-react/kit";
|
|
36
39
|
import eslintJs from "@eslint/js";
|
|
37
40
|
import { defineConfig } from "eslint/config";
|
|
38
41
|
import tseslint from "typescript-eslint";
|
|
39
42
|
|
|
43
|
+
/** Enforce function declarations for function components. */
|
|
44
|
+
function functionComponentDefinition(): RuleDefinition {
|
|
45
|
+
return (context, { collect }) => {
|
|
46
|
+
const { query, visitor } = collect.components(context);
|
|
47
|
+
return merge(
|
|
48
|
+
visitor,
|
|
49
|
+
{
|
|
50
|
+
"Program:exit"(program) {
|
|
51
|
+
for (const { node } of query.all(program)) {
|
|
52
|
+
if (node.type === "FunctionDeclaration") continue;
|
|
53
|
+
context.report({
|
|
54
|
+
node,
|
|
55
|
+
message: "Function components must be defined with function declarations.",
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
40
64
|
export default defineConfig(
|
|
41
65
|
{
|
|
42
66
|
files: ["**/*.{ts,tsx}"],
|
|
@@ -44,55 +68,75 @@ export default defineConfig(
|
|
|
44
68
|
eslintJs.configs.recommended,
|
|
45
69
|
tseslint.configs.recommended,
|
|
46
70
|
eslintReact.configs["recommended-typescript"],
|
|
47
|
-
eslintReactKit(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
make: (context, { collect }) => {
|
|
51
|
-
const { query, visitor } = collect.components(context);
|
|
52
|
-
|
|
53
|
-
return merge(
|
|
54
|
-
visitor,
|
|
55
|
-
{
|
|
56
|
-
"Program:exit"(program) {
|
|
57
|
-
for (const { node } of query.all(program)) {
|
|
58
|
-
if (node.type === "FunctionDeclaration") continue;
|
|
59
|
-
context.report({
|
|
60
|
-
node,
|
|
61
|
-
message: "Function components must be defined with function declarations.",
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
);
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
),
|
|
71
|
+
eslintReactKit()
|
|
72
|
+
.use(functionComponentDefinition)
|
|
73
|
+
.getConfig(),
|
|
70
74
|
],
|
|
71
|
-
rules: {
|
|
72
|
-
"@eslint-react/kit/function-component-definition": "error",
|
|
73
|
-
},
|
|
74
75
|
},
|
|
75
76
|
);
|
|
76
77
|
```
|
|
77
78
|
|
|
79
|
+
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.
|
|
80
|
+
|
|
78
81
|
## API Reference
|
|
79
82
|
|
|
80
|
-
### `
|
|
83
|
+
### `eslintReactKit` (default export)
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import eslintReactKit from "@eslint-react/kit";
|
|
87
|
+
|
|
88
|
+
eslintReactKit(): KitBuilder
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Creates a `KitBuilder` instance for registering custom rules via the chainable `.use()` API.
|
|
92
|
+
|
|
93
|
+
### `RuleDefinition`
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import type { RuleDefinition } from "@eslint-react/kit";
|
|
97
|
+
|
|
98
|
+
type RuleDefinition = (ctx: RuleContext, kit: RuleToolkit) => RuleListener;
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
A rule definition is a function that receives the ESLint rule context and the structured `Kit` toolkit, and returns a `RuleListener` (AST visitor object).
|
|
102
|
+
|
|
103
|
+
Rules are defined as **named functions** that return a `RuleDefinition`. The function name is automatically converted to kebab-case and used as the rule name under the `@eslint-react/kit` plugin namespace.
|
|
81
104
|
|
|
82
105
|
```ts
|
|
83
|
-
|
|
106
|
+
// Function name `noForwardRef` → rule name `no-forward-ref`
|
|
107
|
+
// Registered as `@eslint-react/kit/no-forward-ref`
|
|
108
|
+
function noForwardRef(): RuleDefinition {
|
|
109
|
+
return (context, { is }) => ({ ... });
|
|
110
|
+
}
|
|
84
111
|
|
|
85
|
-
|
|
112
|
+
// Functions that accept options work the same way
|
|
113
|
+
function forbidElements({ forbidden }: ForbidElementsOptions): RuleDefinition {
|
|
114
|
+
return (context) => ({ ... });
|
|
115
|
+
}
|
|
86
116
|
```
|
|
87
117
|
|
|
88
|
-
|
|
118
|
+
### `KitBuilder`
|
|
89
119
|
|
|
90
|
-
|
|
120
|
+
```ts
|
|
121
|
+
interface KitBuilder {
|
|
122
|
+
use<F extends (...args: any[]) => RuleDefinition>(factory: F, ...args: Parameters<F>): KitBuilder;
|
|
123
|
+
getConfig(): Linter.Config;
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
A chainable builder for registering custom rules.
|
|
91
128
|
|
|
92
|
-
|
|
|
93
|
-
|
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
129
|
+
| Method | Description |
|
|
130
|
+
| ----------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
131
|
+
| `use` | Registers a rule factory. The rule name is `kebabCase(factory.name)`. Options type is inferred from the factory signature. |
|
|
132
|
+
| `getConfig` | Returns a `Linter.Config` with all registered rules enabled at `"error"` severity. |
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
eslintReactKit()
|
|
136
|
+
.use(noForwardRef) // no-arg factory
|
|
137
|
+
.use(forbidElements, { forbidden: new Map() }) // factory with inferred options
|
|
138
|
+
.getConfig();
|
|
139
|
+
```
|
|
96
140
|
|
|
97
141
|
### `merge`
|
|
98
142
|
|
|
@@ -108,7 +152,7 @@ This is essential for combining a collector's `visitor` with your own inspection
|
|
|
108
152
|
|
|
109
153
|
### Kit — the toolkit object
|
|
110
154
|
|
|
111
|
-
The second argument to `
|
|
155
|
+
The second argument passed to the `RuleDefinition` function is a structured `Kit` object:
|
|
112
156
|
|
|
113
157
|
```
|
|
114
158
|
kit
|
|
@@ -273,9 +317,8 @@ Exposes the normalized `react-x` settings from the ESLint shared configuration (
|
|
|
273
317
|
**Usage:**
|
|
274
318
|
|
|
275
319
|
```ts
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
make: (context, { settings }) => ({
|
|
320
|
+
function requireReact19(): RuleDefinition {
|
|
321
|
+
return (context, { settings }) => ({
|
|
279
322
|
Program(program) {
|
|
280
323
|
if (!settings.version.startsWith("19.")) {
|
|
281
324
|
context.report({
|
|
@@ -284,8 +327,8 @@ defineReactConfig({
|
|
|
284
327
|
});
|
|
285
328
|
}
|
|
286
329
|
},
|
|
287
|
-
})
|
|
288
|
-
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
289
332
|
```
|
|
290
333
|
|
|
291
334
|
---
|
|
@@ -294,29 +337,32 @@ defineReactConfig({
|
|
|
294
337
|
|
|
295
338
|
### Simple: Ban `forwardRef`
|
|
296
339
|
|
|
297
|
-
This is a simplified kit reimplementation of the built-in [`react-x/no-forwardRef`](https://eslint-react.xyz/docs/rules/no-
|
|
340
|
+
This is a simplified kit reimplementation of the built-in [`react-x/no-forwardRef`](https://beta.eslint-react.xyz/docs/rules/no-forward-ref) rule.
|
|
298
341
|
|
|
299
342
|
```ts
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
make: (context, { is }) => ({
|
|
343
|
+
function noForwardRef(): RuleDefinition {
|
|
344
|
+
return (context, { is }) => ({
|
|
303
345
|
CallExpression(node) {
|
|
304
346
|
if (is.forwardRefCall(node)) {
|
|
305
347
|
context.report({ node, message: "forwardRef is deprecated in React 19. Pass ref as a prop instead." });
|
|
306
348
|
}
|
|
307
349
|
},
|
|
308
|
-
})
|
|
309
|
-
}
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Usage
|
|
354
|
+
eslintReactKit()
|
|
355
|
+
.use(noForwardRef)
|
|
356
|
+
.getConfig();
|
|
310
357
|
```
|
|
311
358
|
|
|
312
359
|
### Component: Destructure component props
|
|
313
360
|
|
|
314
|
-
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.
|
|
361
|
+
This is a simplified kit reimplementation of the built-in [`react-x/prefer-destructuring-assignment`](https://beta.eslint-react.xyz/docs/rules/prefer-destructuring-assignment) rule.
|
|
315
362
|
|
|
316
363
|
```ts
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
make: (context, { collect }) => {
|
|
364
|
+
function destructureComponentProps(): RuleDefinition {
|
|
365
|
+
return (context, { collect }) => {
|
|
320
366
|
const { query, visitor } = collect.components(context);
|
|
321
367
|
|
|
322
368
|
return merge(visitor, {
|
|
@@ -339,18 +385,22 @@ defineReactConfig({
|
|
|
339
385
|
}
|
|
340
386
|
},
|
|
341
387
|
});
|
|
342
|
-
}
|
|
343
|
-
}
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Usage
|
|
392
|
+
eslintReactKit()
|
|
393
|
+
.use(destructureComponentProps)
|
|
394
|
+
.getConfig();
|
|
344
395
|
```
|
|
345
396
|
|
|
346
397
|
### Hooks: Warn on custom hooks that don't call other hooks
|
|
347
398
|
|
|
348
|
-
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.
|
|
399
|
+
This is a simplified kit reimplementation of the built-in [`react-x/no-unnecessary-use-prefix`](https://beta.eslint-react.xyz/docs/rules/no-unnecessary-use-prefix) rule.
|
|
349
400
|
|
|
350
401
|
```ts
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
make: (context, { collect }) => {
|
|
402
|
+
function noUnnecessaryUsePrefix(): RuleDefinition {
|
|
403
|
+
return (context, { collect }) => {
|
|
354
404
|
const { query, visitor } = collect.hooks(context);
|
|
355
405
|
|
|
356
406
|
return merge(visitor, {
|
|
@@ -365,19 +415,34 @@ defineReactConfig({
|
|
|
365
415
|
}
|
|
366
416
|
},
|
|
367
417
|
});
|
|
368
|
-
}
|
|
369
|
-
}
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Usage
|
|
422
|
+
eslintReactKit()
|
|
423
|
+
.use(noUnnecessaryUsePrefix)
|
|
424
|
+
.getConfig();
|
|
370
425
|
```
|
|
371
426
|
|
|
372
427
|
### Multiple Collectors: No component/hook factories
|
|
373
428
|
|
|
374
429
|
Disallow defining components or hooks inside other functions (factory pattern).
|
|
375
|
-
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.
|
|
430
|
+
This is a simplified kit reimplementation of the built-in [`react-x/component-hook-factories`](https://beta.eslint-react.xyz/docs/rules/component-hook-factories) rule.
|
|
376
431
|
|
|
377
432
|
```ts
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
433
|
+
function findParent({ parent }: TSESTree.Node, test: (n: TSESTree.Node) => boolean): TSESTree.Node | null {
|
|
434
|
+
if (parent == null) return null;
|
|
435
|
+
if (test(parent)) return parent;
|
|
436
|
+
if (parent.type === "Program") return null;
|
|
437
|
+
return findParent(parent, test);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function isFunction({ type }: TSESTree.Node) {
|
|
441
|
+
return type === "FunctionDeclaration" || type === "FunctionExpression" || type === "ArrowFunctionExpression";
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function componentHookFactories(): RuleDefinition {
|
|
445
|
+
return (context, { collect }) => {
|
|
381
446
|
const fc = collect.components(context);
|
|
382
447
|
const hk = collect.hooks(context);
|
|
383
448
|
return merge(
|
|
@@ -398,21 +463,15 @@ defineReactConfig({
|
|
|
398
463
|
},
|
|
399
464
|
},
|
|
400
465
|
);
|
|
401
|
-
}
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
function findParent({ parent }: TSESTree.Node, test: (n: TSESTree.Node) => boolean): TSESTree.Node | null {
|
|
405
|
-
if (parent == null) return null;
|
|
406
|
-
if (test(parent)) return parent;
|
|
407
|
-
if (parent.type === "Program") return null;
|
|
408
|
-
return findParent(parent, test);
|
|
466
|
+
};
|
|
409
467
|
}
|
|
410
468
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
469
|
+
// Usage
|
|
470
|
+
eslintReactKit()
|
|
471
|
+
.use(componentHookFactories)
|
|
472
|
+
.getConfig();
|
|
414
473
|
```
|
|
415
474
|
|
|
416
475
|
## More Examples
|
|
417
476
|
|
|
418
|
-
Please check the [Rule Recipes](https://eslint-react.xyz/docs/configuration/configure-custom-rules#rule-recipes) in the documentation site.
|
|
477
|
+
Please check the [Rule Recipes](https://beta.eslint-react.xyz/docs/configuration/configure-custom-rules#rule-recipes) in the documentation site.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as core from "@eslint-react/core";
|
|
2
|
-
import { ESLintReactSettingsNormalized } from "@eslint-react/shared";
|
|
2
|
+
import { ESLintReactSettingsNormalized, RuleFix, RuleFixer, RuleListener, defineRuleListener as merge } from "@eslint-react/shared";
|
|
3
3
|
import { TSESTreeFunction } from "@eslint-react/ast";
|
|
4
4
|
import { TSESTree } from "@typescript-eslint/utils";
|
|
5
|
-
import { RuleContext
|
|
5
|
+
import { RuleContext } from "@typescript-eslint/utils/ts-eslint";
|
|
6
6
|
import { Linter } from "eslint";
|
|
7
7
|
|
|
8
8
|
//#region src/index.d.ts
|
|
@@ -17,33 +17,79 @@ interface CollectorWithContext<T> extends Collector<T> {
|
|
|
17
17
|
all(program: TSESTree.Program): T[];
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
type RuleDefinition = (ctx: RuleContext, kit: RuleToolkit) => RuleListener;
|
|
21
|
+
interface KitBuilder {
|
|
22
|
+
getConfig(args?: {
|
|
23
|
+
files?: string[];
|
|
24
|
+
}): Linter.Config;
|
|
25
|
+
use<F extends (...args: any[]) => RuleDefinition>(factory: F, ...args: Parameters<F>): KitBuilder;
|
|
26
|
+
}
|
|
25
27
|
interface RuleToolkit {
|
|
28
|
+
collect: {
|
|
29
|
+
components(ctx: RuleContext, options?: {
|
|
30
|
+
collectDisplayName?: boolean;
|
|
31
|
+
hint?: bigint;
|
|
32
|
+
}): CollectorWithContext<core.FunctionComponentSemanticNode>;
|
|
33
|
+
hooks(ctx: RuleContext): CollectorWithContext<core.HookSemanticNode>;
|
|
34
|
+
};
|
|
35
|
+
flag: {
|
|
36
|
+
component: typeof core.ComponentFlag;
|
|
37
|
+
};
|
|
38
|
+
hint: {
|
|
39
|
+
component: typeof core.ComponentDetectionHint & {
|
|
40
|
+
Default: bigint;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
26
43
|
is: {
|
|
44
|
+
captureOwnerStack: (node: null | TSESTree.Node) => boolean;
|
|
45
|
+
captureOwnerStackCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
46
|
+
childrenCount: (node: null | TSESTree.Node) => boolean;
|
|
47
|
+
childrenCountCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
48
|
+
childrenForEach: (node: null | TSESTree.Node) => boolean;
|
|
49
|
+
childrenForEachCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
50
|
+
childrenMap: (node: null | TSESTree.Node) => boolean;
|
|
51
|
+
childrenMapCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
52
|
+
childrenOnly: (node: null | TSESTree.Node) => boolean;
|
|
53
|
+
childrenOnlyCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
54
|
+
childrenToArray: (node: null | TSESTree.Node) => boolean;
|
|
55
|
+
childrenToArrayCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
56
|
+
cloneElement: (node: null | TSESTree.Node) => boolean;
|
|
57
|
+
cloneElementCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
27
58
|
componentDefinition: (node: TSESTreeFunction, hint: bigint) => boolean;
|
|
28
59
|
componentName: typeof core.isComponentName;
|
|
29
60
|
componentNameLoose: typeof core.isComponentNameLoose;
|
|
30
61
|
componentWrapperCall: (node: TSESTree.Node) => boolean;
|
|
31
|
-
componentWrapperCallLoose: (node: TSESTree.Node) => boolean;
|
|
32
62
|
componentWrapperCallback: (node: TSESTree.Node) => boolean;
|
|
63
|
+
componentWrapperCallLoose: (node: TSESTree.Node) => boolean;
|
|
64
|
+
createContext: (node: null | TSESTree.Node) => boolean;
|
|
65
|
+
createContextCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
66
|
+
createElement: (node: null | TSESTree.Node) => boolean;
|
|
67
|
+
createElementCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
68
|
+
createRef: (node: null | TSESTree.Node) => boolean;
|
|
69
|
+
createRefCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
70
|
+
forwardRef: (node: null | TSESTree.Node) => boolean;
|
|
71
|
+
forwardRefCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
33
72
|
hook: typeof core.isHook;
|
|
34
73
|
hookCall: typeof core.isHookCall;
|
|
35
74
|
hookName: typeof core.isHookName;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
75
|
+
initializedFromReact: typeof core.isInitializedFromReact;
|
|
76
|
+
initializedFromReactNative: typeof core.isInitializedFromReactNative;
|
|
77
|
+
lazy: (node: null | TSESTree.Node) => boolean;
|
|
78
|
+
lazyCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
79
|
+
memo: (node: null | TSESTree.Node) => boolean;
|
|
80
|
+
memoCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
81
|
+
reactAPI: (api: string) => (node: null | TSESTree.Node) => boolean;
|
|
82
|
+
reactAPICall: (api: string) => (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
41
83
|
useActionStateCall: typeof core.isUseActionStateCall;
|
|
84
|
+
useCall: typeof core.isUseCall;
|
|
42
85
|
useCallbackCall: typeof core.isUseCallbackCall;
|
|
43
86
|
useContextCall: typeof core.isUseContextCall;
|
|
44
87
|
useDebugValueCall: typeof core.isUseDebugValueCall;
|
|
45
88
|
useDeferredValueCall: typeof core.isUseDeferredValueCall;
|
|
46
89
|
useEffectCall: typeof core.isUseEffectCall;
|
|
90
|
+
useEffectCleanupCallback: typeof core.isUseEffectCleanupCallback;
|
|
91
|
+
useEffectLikeCall: typeof core.isUseEffectLikeCall;
|
|
92
|
+
useEffectSetupCallback: typeof core.isUseEffectSetupCallback;
|
|
47
93
|
useFormStatusCall: typeof core.isUseFormStatusCall;
|
|
48
94
|
useIdCall: typeof core.isUseIdCall;
|
|
49
95
|
useImperativeHandleCall: typeof core.isUseImperativeHandleCall;
|
|
@@ -54,61 +100,15 @@ interface RuleToolkit {
|
|
|
54
100
|
useReducerCall: typeof core.isUseReducerCall;
|
|
55
101
|
useRefCall: typeof core.isUseRefCall;
|
|
56
102
|
useStateCall: typeof core.isUseStateCall;
|
|
103
|
+
useStateLikeCall: typeof core.isUseStateLikeCall;
|
|
57
104
|
useSyncExternalStoreCall: typeof core.isUseSyncExternalStoreCall;
|
|
58
105
|
useTransitionCall: typeof core.isUseTransitionCall;
|
|
59
|
-
reactAPI: (api: string) => (node: null | TSESTree.Node) => boolean;
|
|
60
|
-
reactAPICall: (api: string) => (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
61
|
-
captureOwnerStack: (node: null | TSESTree.Node) => boolean;
|
|
62
|
-
childrenCount: (node: null | TSESTree.Node) => boolean;
|
|
63
|
-
childrenForEach: (node: null | TSESTree.Node) => boolean;
|
|
64
|
-
childrenMap: (node: null | TSESTree.Node) => boolean;
|
|
65
|
-
childrenOnly: (node: null | TSESTree.Node) => boolean;
|
|
66
|
-
childrenToArray: (node: null | TSESTree.Node) => boolean;
|
|
67
|
-
cloneElement: (node: null | TSESTree.Node) => boolean;
|
|
68
|
-
createContext: (node: null | TSESTree.Node) => boolean;
|
|
69
|
-
createElement: (node: null | TSESTree.Node) => boolean;
|
|
70
|
-
createRef: (node: null | TSESTree.Node) => boolean;
|
|
71
|
-
forwardRef: (node: null | TSESTree.Node) => boolean;
|
|
72
|
-
memo: (node: null | TSESTree.Node) => boolean;
|
|
73
|
-
lazy: (node: null | TSESTree.Node) => boolean;
|
|
74
|
-
captureOwnerStackCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
75
|
-
childrenCountCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
76
|
-
childrenForEachCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
77
|
-
childrenMapCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
78
|
-
childrenOnlyCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
79
|
-
childrenToArrayCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
80
|
-
cloneElementCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
81
|
-
createContextCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
82
|
-
createElementCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
83
|
-
createRefCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
84
|
-
forwardRefCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
85
|
-
memoCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
86
|
-
lazyCall: (node: null | TSESTree.Node) => node is TSESTree.CallExpression;
|
|
87
|
-
initializedFromReact: typeof core.isInitializedFromReact;
|
|
88
|
-
initializedFromReactNative: typeof core.isInitializedFromReactNative;
|
|
89
|
-
};
|
|
90
|
-
hint: {
|
|
91
|
-
component: typeof core.ComponentDetectionHint & {
|
|
92
|
-
Default: bigint;
|
|
93
|
-
};
|
|
94
|
-
};
|
|
95
|
-
flag: {
|
|
96
|
-
component: typeof core.ComponentFlag;
|
|
97
|
-
};
|
|
98
|
-
collect: {
|
|
99
|
-
components: typeof components;
|
|
100
|
-
hooks: typeof hooks;
|
|
101
106
|
};
|
|
102
107
|
settings: ESLintReactSettingsNormalized;
|
|
103
108
|
}
|
|
104
|
-
|
|
105
|
-
name: string;
|
|
106
|
-
make(ctx: RuleContext<string, unknown[]>, kit: RuleToolkit): RuleListener;
|
|
107
|
-
}
|
|
108
|
-
declare function defineConfig(...rules: RuleDefinition[]): Linter.Config;
|
|
109
|
-
declare function merge(...listeners: RuleListener[]): RuleListener;
|
|
109
|
+
declare function eslintReactKit(): KitBuilder;
|
|
110
110
|
declare module "@typescript-eslint/utils/ts-eslint" {
|
|
111
|
-
interface RuleContext<MessageIds extends string, Options extends readonly unknown[]> {
|
|
111
|
+
interface RuleContext<MessageIds extends string = string, Options extends readonly unknown[] = readonly unknown[]> {
|
|
112
112
|
report(descriptor: {
|
|
113
113
|
readonly data?: Readonly<Record<string, unknown>>;
|
|
114
114
|
readonly fix?: ((fixer: RuleFixer) => IterableIterator<RuleFix> | readonly RuleFix[] | RuleFix | null) | null;
|
|
@@ -124,4 +124,4 @@ declare module "@typescript-eslint/utils/ts-eslint" {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
//#endregion
|
|
127
|
-
export { Collector, CollectorWithContext, type
|
|
127
|
+
export { Collector, CollectorWithContext, KitBuilder, RuleDefinition, type RuleFix, type RuleFixer, type RuleListener, eslintReactKit as default, merge };
|
package/dist/index.js
CHANGED
|
@@ -1,49 +1,90 @@
|
|
|
1
1
|
import * as core from "@eslint-react/core";
|
|
2
|
-
import { getSettingsFromContext } from "@eslint-react/shared";
|
|
2
|
+
import { IdGenerator, defineRuleListener as merge, getSettingsFromContext } from "@eslint-react/shared";
|
|
3
|
+
import { kebabCase } from "string-ts";
|
|
3
4
|
|
|
4
5
|
//#region package.json
|
|
5
6
|
var name = "@eslint-react/kit";
|
|
6
|
-
var version = "4.0.
|
|
7
|
+
var version = "4.0.1-beta.0";
|
|
7
8
|
|
|
8
9
|
//#endregion
|
|
9
10
|
//#region src/index.ts
|
|
10
|
-
function components(ctx, options) {
|
|
11
|
-
const { api, visitor } = core.getComponentCollector(ctx, options);
|
|
12
|
-
return {
|
|
13
|
-
query: { all: (program) => api.getAllComponents(program) },
|
|
14
|
-
visitor
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
function hooks(ctx) {
|
|
18
|
-
const { api, visitor } = core.getHookCollector(ctx);
|
|
19
|
-
return {
|
|
20
|
-
query: { all: (program) => api.getAllHooks(program) },
|
|
21
|
-
visitor
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
11
|
function createKit(ctx) {
|
|
25
12
|
return {
|
|
13
|
+
collect: {
|
|
14
|
+
components(ctx, options) {
|
|
15
|
+
const { api, visitor } = core.getComponentCollector(ctx, options);
|
|
16
|
+
return {
|
|
17
|
+
query: { all(program) {
|
|
18
|
+
return api.getAllComponents(program);
|
|
19
|
+
} },
|
|
20
|
+
visitor
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
hooks(ctx) {
|
|
24
|
+
const { api, visitor } = core.getHookCollector(ctx);
|
|
25
|
+
return {
|
|
26
|
+
query: { all(program) {
|
|
27
|
+
return api.getAllHooks(program);
|
|
28
|
+
} },
|
|
29
|
+
visitor
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
flag: { component: core.ComponentFlag },
|
|
34
|
+
hint: { component: {
|
|
35
|
+
...core.ComponentDetectionHint,
|
|
36
|
+
Default: core.DEFAULT_COMPONENT_DETECTION_HINT
|
|
37
|
+
} },
|
|
26
38
|
is: {
|
|
39
|
+
captureOwnerStack: core.isCaptureOwnerStack(ctx),
|
|
40
|
+
captureOwnerStackCall: core.isCaptureOwnerStackCall(ctx),
|
|
41
|
+
childrenCount: core.isChildrenCount(ctx),
|
|
42
|
+
childrenCountCall: core.isChildrenCountCall(ctx),
|
|
43
|
+
childrenForEach: core.isChildrenForEach(ctx),
|
|
44
|
+
childrenForEachCall: core.isChildrenForEachCall(ctx),
|
|
45
|
+
childrenMap: core.isChildrenMap(ctx),
|
|
46
|
+
childrenMapCall: core.isChildrenMapCall(ctx),
|
|
47
|
+
childrenOnly: core.isChildrenOnly(ctx),
|
|
48
|
+
childrenOnlyCall: core.isChildrenOnlyCall(ctx),
|
|
49
|
+
childrenToArray: core.isChildrenToArray(ctx),
|
|
50
|
+
childrenToArrayCall: core.isChildrenToArrayCall(ctx),
|
|
51
|
+
cloneElement: core.isCloneElement(ctx),
|
|
52
|
+
cloneElementCall: core.isCloneElementCall(ctx),
|
|
27
53
|
componentDefinition: (node, hint) => core.isComponentDefinition(ctx, node, hint),
|
|
28
54
|
componentName: core.isComponentName,
|
|
29
55
|
componentNameLoose: core.isComponentNameLoose,
|
|
30
56
|
componentWrapperCall: (node) => core.isComponentWrapperCall(ctx, node),
|
|
31
|
-
componentWrapperCallLoose: (node) => core.isComponentWrapperCallLoose(ctx, node),
|
|
32
57
|
componentWrapperCallback: (node) => core.isComponentWrapperCallback(ctx, node),
|
|
58
|
+
componentWrapperCallLoose: (node) => core.isComponentWrapperCallLoose(ctx, node),
|
|
59
|
+
createContext: core.isCreateContext(ctx),
|
|
60
|
+
createContextCall: core.isCreateContextCall(ctx),
|
|
61
|
+
createElement: core.isCreateElement(ctx),
|
|
62
|
+
createElementCall: core.isCreateElementCall(ctx),
|
|
63
|
+
createRef: core.isCreateRef(ctx),
|
|
64
|
+
createRefCall: core.isCreateRefCall(ctx),
|
|
65
|
+
forwardRef: core.isForwardRef(ctx),
|
|
66
|
+
forwardRefCall: core.isForwardRefCall(ctx),
|
|
33
67
|
hook: core.isHook,
|
|
34
68
|
hookCall: core.isHookCall,
|
|
35
69
|
hookName: core.isHookName,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
70
|
+
initializedFromReact: core.isInitializedFromReact,
|
|
71
|
+
initializedFromReactNative: core.isInitializedFromReactNative,
|
|
72
|
+
lazy: core.isLazy(ctx),
|
|
73
|
+
lazyCall: core.isLazyCall(ctx),
|
|
74
|
+
memo: core.isMemo(ctx),
|
|
75
|
+
memoCall: core.isMemoCall(ctx),
|
|
76
|
+
reactAPI: (api) => core.isReactAPI(api)(ctx),
|
|
77
|
+
reactAPICall: (api) => core.isReactAPICall(api)(ctx),
|
|
41
78
|
useActionStateCall: core.isUseActionStateCall,
|
|
79
|
+
useCall: core.isUseCall,
|
|
42
80
|
useCallbackCall: core.isUseCallbackCall,
|
|
43
81
|
useContextCall: core.isUseContextCall,
|
|
44
82
|
useDebugValueCall: core.isUseDebugValueCall,
|
|
45
83
|
useDeferredValueCall: core.isUseDeferredValueCall,
|
|
46
84
|
useEffectCall: core.isUseEffectCall,
|
|
85
|
+
useEffectCleanupCallback: core.isUseEffectCleanupCallback,
|
|
86
|
+
useEffectLikeCall: core.isUseEffectLikeCall,
|
|
87
|
+
useEffectSetupCallback: core.isUseEffectSetupCallback,
|
|
47
88
|
useFormStatusCall: core.isUseFormStatusCall,
|
|
48
89
|
useIdCall: core.isUseIdCall,
|
|
49
90
|
useImperativeHandleCall: core.isUseImperativeHandleCall,
|
|
@@ -54,89 +95,55 @@ function createKit(ctx) {
|
|
|
54
95
|
useReducerCall: core.isUseReducerCall,
|
|
55
96
|
useRefCall: core.isUseRefCall,
|
|
56
97
|
useStateCall: core.isUseStateCall,
|
|
98
|
+
useStateLikeCall: core.isUseStateLikeCall,
|
|
57
99
|
useSyncExternalStoreCall: core.isUseSyncExternalStoreCall,
|
|
58
|
-
useTransitionCall: core.isUseTransitionCall
|
|
59
|
-
reactAPI: (api) => core.isReactAPI(api)(ctx),
|
|
60
|
-
reactAPICall: (api) => core.isReactAPICall(api)(ctx),
|
|
61
|
-
captureOwnerStack: core.isCaptureOwnerStack(ctx),
|
|
62
|
-
childrenCount: core.isChildrenCount(ctx),
|
|
63
|
-
childrenForEach: core.isChildrenForEach(ctx),
|
|
64
|
-
childrenMap: core.isChildrenMap(ctx),
|
|
65
|
-
childrenOnly: core.isChildrenOnly(ctx),
|
|
66
|
-
childrenToArray: core.isChildrenToArray(ctx),
|
|
67
|
-
cloneElement: core.isCloneElement(ctx),
|
|
68
|
-
createContext: core.isCreateContext(ctx),
|
|
69
|
-
createElement: core.isCreateElement(ctx),
|
|
70
|
-
createRef: core.isCreateRef(ctx),
|
|
71
|
-
forwardRef: core.isForwardRef(ctx),
|
|
72
|
-
memo: core.isMemo(ctx),
|
|
73
|
-
lazy: core.isLazy(ctx),
|
|
74
|
-
captureOwnerStackCall: core.isCaptureOwnerStackCall(ctx),
|
|
75
|
-
childrenCountCall: core.isChildrenCountCall(ctx),
|
|
76
|
-
childrenForEachCall: core.isChildrenForEachCall(ctx),
|
|
77
|
-
childrenMapCall: core.isChildrenMapCall(ctx),
|
|
78
|
-
childrenOnlyCall: core.isChildrenOnlyCall(ctx),
|
|
79
|
-
childrenToArrayCall: core.isChildrenToArrayCall(ctx),
|
|
80
|
-
cloneElementCall: core.isCloneElementCall(ctx),
|
|
81
|
-
createContextCall: core.isCreateContextCall(ctx),
|
|
82
|
-
createElementCall: core.isCreateElementCall(ctx),
|
|
83
|
-
createRefCall: core.isCreateRefCall(ctx),
|
|
84
|
-
forwardRefCall: core.isForwardRefCall(ctx),
|
|
85
|
-
memoCall: core.isMemoCall(ctx),
|
|
86
|
-
lazyCall: core.isLazyCall(ctx),
|
|
87
|
-
initializedFromReact: core.isInitializedFromReact,
|
|
88
|
-
initializedFromReactNative: core.isInitializedFromReactNative
|
|
89
|
-
},
|
|
90
|
-
hint: { component: {
|
|
91
|
-
...core.ComponentDetectionHint,
|
|
92
|
-
Default: core.DEFAULT_COMPONENT_DETECTION_HINT
|
|
93
|
-
} },
|
|
94
|
-
flag: { component: core.ComponentFlag },
|
|
95
|
-
collect: {
|
|
96
|
-
components,
|
|
97
|
-
hooks
|
|
100
|
+
useTransitionCall: core.isUseTransitionCall
|
|
98
101
|
},
|
|
99
102
|
settings: getSettingsFromContext(ctx)
|
|
100
103
|
};
|
|
101
104
|
}
|
|
102
|
-
function
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
rules: rules.reduce((acc, { name, make }) => {
|
|
111
|
-
Reflect.set(acc, name, {
|
|
105
|
+
function eslintReactKit() {
|
|
106
|
+
const idGen = new IdGenerator();
|
|
107
|
+
const rules = [];
|
|
108
|
+
const builder = {
|
|
109
|
+
getConfig({ files = ["**/*.ts", "**/*.tsx"] } = {}) {
|
|
110
|
+
return {
|
|
111
|
+
files,
|
|
112
|
+
plugins: { [name]: {
|
|
112
113
|
meta: {
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
name,
|
|
115
|
+
version
|
|
115
116
|
},
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
117
|
+
rules: rules.reduce((acc, { name, make }) => {
|
|
118
|
+
Reflect.set(acc, name, {
|
|
119
|
+
meta: {
|
|
120
|
+
fixable: "code",
|
|
121
|
+
hasSuggestions: true
|
|
122
|
+
},
|
|
123
|
+
create(ctx) {
|
|
124
|
+
return make(ctx, createKit(ctx));
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
return acc;
|
|
128
|
+
}, {})
|
|
129
|
+
} },
|
|
130
|
+
rules: rules.reduce((acc, { name: name$1 }) => {
|
|
131
|
+
acc[`${name}/${name$1}`] = "error";
|
|
132
|
+
return acc;
|
|
133
|
+
}, {})
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
use(factory, ...args) {
|
|
137
|
+
const name = kebabCase(factory.name === "" ? idGen.next() : factory.name);
|
|
138
|
+
rules.push({
|
|
139
|
+
name,
|
|
140
|
+
make: factory(...args)
|
|
141
|
+
});
|
|
142
|
+
return builder;
|
|
143
|
+
}
|
|
127
144
|
};
|
|
128
|
-
|
|
129
|
-
function merge(...listeners) {
|
|
130
|
-
const [base = {}, ...rest] = listeners;
|
|
131
|
-
for (const r of rest) for (const key in r) {
|
|
132
|
-
const existing = base[key];
|
|
133
|
-
base[key] = existing ? (...args) => {
|
|
134
|
-
existing(...args);
|
|
135
|
-
r[key]?.(...args);
|
|
136
|
-
} : r[key];
|
|
137
|
-
}
|
|
138
|
-
return base;
|
|
145
|
+
return builder;
|
|
139
146
|
}
|
|
140
147
|
|
|
141
148
|
//#endregion
|
|
142
|
-
export {
|
|
149
|
+
export { eslintReactKit as default, merge };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eslint-react/kit",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.1-beta.0",
|
|
4
4
|
"description": "ESLint React's utility module for building custom rules.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -37,13 +37,14 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@typescript-eslint/utils": "^8.57.2",
|
|
40
|
-
"
|
|
41
|
-
"@eslint-react/
|
|
42
|
-
"@eslint-react/
|
|
40
|
+
"string-ts": "^2.3.1",
|
|
41
|
+
"@eslint-react/core": "4.0.1-beta.0",
|
|
42
|
+
"@eslint-react/shared": "4.0.1-beta.0",
|
|
43
|
+
"@eslint-react/ast": "4.0.1-beta.0"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"eslint": "^10.1.0",
|
|
46
|
-
"tsdown": "^0.21.
|
|
47
|
+
"tsdown": "^0.21.6",
|
|
47
48
|
"@local/configs": "0.0.0",
|
|
48
49
|
"@local/eff": "3.0.0-beta.72"
|
|
49
50
|
},
|