@eslint-react/kit 4.0.0-beta.3 → 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 +130 -71
- package/dist/index.d.ts +8 -5
- package/dist/index.js +42 -26
- package/package.json +5 -4
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
|
|
@@ -33,10 +35,32 @@ npm install --save-dev @eslint-react/kit@beta
|
|
|
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
|
---
|
|
@@ -297,16 +340,20 @@ defineReactConfig({
|
|
|
297
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
|
|
@@ -314,9 +361,8 @@ defineReactConfig({
|
|
|
314
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,8 +385,13 @@ 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
|
|
@@ -348,9 +399,8 @@ defineReactConfig({
|
|
|
348
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,8 +415,13 @@ 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
|
|
@@ -375,9 +430,19 @@ Disallow defining components or hooks inside other functions (factory pattern).
|
|
|
375
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,19 +463,13 @@ 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
|
package/dist/index.d.ts
CHANGED
|
@@ -17,9 +17,12 @@ interface CollectorWithContext<T> extends Collector<T> {
|
|
|
17
17
|
all(program: TSESTree.Program): T[];
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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;
|
|
23
26
|
}
|
|
24
27
|
interface RuleToolkit {
|
|
25
28
|
collect: {
|
|
@@ -103,7 +106,7 @@ interface RuleToolkit {
|
|
|
103
106
|
};
|
|
104
107
|
settings: ESLintReactSettingsNormalized;
|
|
105
108
|
}
|
|
106
|
-
declare function
|
|
109
|
+
declare function eslintReactKit(): KitBuilder;
|
|
107
110
|
declare module "@typescript-eslint/utils/ts-eslint" {
|
|
108
111
|
interface RuleContext<MessageIds extends string = string, Options extends readonly unknown[] = readonly unknown[]> {
|
|
109
112
|
report(descriptor: {
|
|
@@ -121,4 +124,4 @@ declare module "@typescript-eslint/utils/ts-eslint" {
|
|
|
121
124
|
}
|
|
122
125
|
}
|
|
123
126
|
//#endregion
|
|
124
|
-
export { Collector, CollectorWithContext, RuleDefinition, type RuleFix, type RuleFixer, type RuleListener,
|
|
127
|
+
export { Collector, CollectorWithContext, KitBuilder, RuleDefinition, type RuleFix, type RuleFixer, type RuleListener, eslintReactKit as default, merge };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as core from "@eslint-react/core";
|
|
2
|
-
import { defineRuleListener as merge, 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
|
|
@@ -101,33 +102,48 @@ function createKit(ctx) {
|
|
|
101
102
|
settings: getSettingsFromContext(ctx)
|
|
102
103
|
};
|
|
103
104
|
}
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
rules: rules.reduce((acc, { name, make }) => {
|
|
113
|
-
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]: {
|
|
114
113
|
meta: {
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
name,
|
|
115
|
+
version
|
|
117
116
|
},
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
+
}
|
|
129
144
|
};
|
|
145
|
+
return builder;
|
|
130
146
|
}
|
|
131
147
|
|
|
132
148
|
//#endregion
|
|
133
|
-
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,9 +37,10 @@
|
|
|
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",
|