@eslint-react/kit 4.0.1-beta.0 → 4.0.2-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 CHANGED
@@ -9,7 +9,9 @@ ESLint React's toolkit for building custom React lint rules right inside your `e
9
9
  - [API Reference](#api-reference)
10
10
  - [`eslintReactKit` (default export)](#eslintreactkit-default-export)
11
11
  - [`RuleDefinition`](#ruledefinition)
12
- - [`KitBuilder`](#kitbuilder)
12
+ - [`Builder`](#builder)
13
+ - [`getConfig`](#getconfig)
14
+ - [`getPlugin`](#getplugin)
13
15
  - [`merge`](#merge)
14
16
  - [`Kit` — the toolkit object](#kit--the-toolkit-object)
15
17
  - [`kit.collect`](#kitcollect) — Semantic collectors
@@ -22,6 +24,7 @@ ESLint React's toolkit for building custom React lint rules right inside your `e
22
24
  - [Component: Destructure component props](#component-destructure-component-props)
23
25
  - [Hooks: Warn on custom hooks that don't call other hooks](#hooks-warn-on-custom-hooks-that-dont-call-other-hooks)
24
26
  - [Multiple Collectors: No component/hook factories](#multiple-collectors-no-componenthook-factories)
27
+ - [Advanced Config: Using `getPlugin` for custom plugin namespace](#advanced-config-using-getplugin-for-custom-plugin-namespace)
25
28
  - [More Examples](#more-examples)
26
29
 
27
30
  ## Installation
@@ -85,10 +88,10 @@ The rule name is derived automatically from the function name (`functionComponen
85
88
  ```ts
86
89
  import eslintReactKit from "@eslint-react/kit";
87
90
 
88
- eslintReactKit(): KitBuilder
91
+ eslintReactKit(): Builder
89
92
  ```
90
93
 
91
- Creates a `KitBuilder` instance for registering custom rules via the chainable `.use()` API.
94
+ Creates a `Builder` instance for registering custom rules via the chainable `.use()` API.
92
95
 
93
96
  ### `RuleDefinition`
94
97
 
@@ -115,12 +118,13 @@ function forbidElements({ forbidden }: ForbidElementsOptions): RuleDefinition {
115
118
  }
116
119
  ```
117
120
 
118
- ### `KitBuilder`
121
+ ### `Builder`
119
122
 
120
123
  ```ts
121
- interface KitBuilder {
122
- use<F extends (...args: any[]) => RuleDefinition>(factory: F, ...args: Parameters<F>): KitBuilder;
123
- getConfig(): Linter.Config;
124
+ interface Builder {
125
+ use<F extends (...args: any[]) => RuleDefinition>(factory: F, ...args: Parameters<F>): Builder;
126
+ getConfig(args?: { files?: string[] }): Linter.Config;
127
+ getPlugin(): ESLint.Plugin;
124
128
  }
125
129
  ```
126
130
 
@@ -130,6 +134,11 @@ A chainable builder for registering custom rules.
130
134
  | ----------- | -------------------------------------------------------------------------------------------------------------------------- |
131
135
  | `use` | Registers a rule factory. The rule name is `kebabCase(factory.name)`. Options type is inferred from the factory signature. |
132
136
  | `getConfig` | Returns a `Linter.Config` with all registered rules enabled at `"error"` severity. |
137
+ | `getPlugin` | Returns an `ESLint.Plugin` containing the registered rules and plugin metadata. |
138
+
139
+ #### `getConfig`
140
+
141
+ 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.
133
142
 
134
143
  ```ts
135
144
  eslintReactKit()
@@ -138,6 +147,34 @@ eslintReactKit()
138
147
  .getConfig();
139
148
  ```
140
149
 
150
+ #### `getPlugin`
151
+
152
+ 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.
153
+
154
+ ```ts
155
+ const kit = eslintReactKit()
156
+ .use(noForwardRef)
157
+ .use(forbidElements, { forbidden: new Map() });
158
+
159
+ // Retrieve the raw plugin object
160
+ const plugin = kit.getPlugin();
161
+ // => { meta: { name: "@eslint-react/kit", version: "..." }, rules: { "no-forward-ref": ..., "forbid-elements": ... } }
162
+
163
+ // Use it in a custom flat config with your own namespace and severity
164
+ export default [
165
+ {
166
+ files: ["**/*.{ts,tsx}"],
167
+ plugins: {
168
+ "react-custom-rules": plugin,
169
+ },
170
+ rules: {
171
+ "react-custom-rules/no-forward-ref": "error",
172
+ "react-custom-rules/forbid-elements": "warn",
173
+ },
174
+ },
175
+ ];
176
+ ```
177
+
141
178
  ### `merge`
142
179
 
143
180
  ```ts
@@ -317,6 +354,8 @@ Exposes the normalized `react-x` settings from the ESLint shared configuration (
317
354
  **Usage:**
318
355
 
319
356
  ```ts
357
+ import type { RuleDefinition } from "@eslint-react/kit";
358
+
320
359
  function requireReact19(): RuleDefinition {
321
360
  return (context, { settings }) => ({
322
361
  Program(program) {
@@ -340,6 +379,8 @@ function requireReact19(): RuleDefinition {
340
379
  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.
341
380
 
342
381
  ```ts
382
+ import type { RuleDefinition } from "@eslint-react/kit";
383
+
343
384
  function noForwardRef(): RuleDefinition {
344
385
  return (context, { is }) => ({
345
386
  CallExpression(node) {
@@ -361,6 +402,9 @@ eslintReactKit()
361
402
  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.
362
403
 
363
404
  ```ts
405
+ import type { RuleDefinition } from "@eslint-react/kit";
406
+ import { merge } from "@eslint-react/kit";
407
+
364
408
  function destructureComponentProps(): RuleDefinition {
365
409
  return (context, { collect }) => {
366
410
  const { query, visitor } = collect.components(context);
@@ -370,13 +414,13 @@ function destructureComponentProps(): RuleDefinition {
370
414
  for (const { node } of query.all(program)) {
371
415
  const [props] = node.params;
372
416
  if (props == null) continue;
373
- if (props.type !== AST.Identifier) continue;
417
+ if (props.type !== "Identifier") continue;
374
418
  const propName = props.name;
375
419
  const propVariable = context.sourceCode.getScope(node).variables.find((v) => v.name === propName);
376
420
  const propReferences = propVariable?.references ?? [];
377
421
  for (const ref of propReferences) {
378
422
  const { parent } = ref.identifier;
379
- if (parent.type !== AST.MemberExpression) continue;
423
+ if (parent.type !== "MemberExpression") continue;
380
424
  context.report({
381
425
  message: "Use destructuring assignment for component props.",
382
426
  node: parent,
@@ -399,6 +443,9 @@ eslintReactKit()
399
443
  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.
400
444
 
401
445
  ```ts
446
+ import type { RuleDefinition } from "@eslint-react/kit";
447
+ import { merge } from "@eslint-react/kit";
448
+
402
449
  function noUnnecessaryUsePrefix(): RuleDefinition {
403
450
  return (context, { collect }) => {
404
451
  const { query, visitor } = collect.hooks(context);
@@ -430,6 +477,10 @@ Disallow defining components or hooks inside other functions (factory pattern).
430
477
  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.
431
478
 
432
479
  ```ts
480
+ import type { RuleDefinition } from "@eslint-react/kit";
481
+ import { merge } from "@eslint-react/kit";
482
+ import type { TSESTree } from "@typescript-eslint/utils";
483
+
433
484
  function findParent({ parent }: TSESTree.Node, test: (n: TSESTree.Node) => boolean): TSESTree.Node | null {
434
485
  if (parent == null) return null;
435
486
  if (test(parent)) return parent;
@@ -472,6 +523,60 @@ eslintReactKit()
472
523
  .getConfig();
473
524
  ```
474
525
 
475
- ## More Examples
526
+ ### Advanced Config: Using `getPlugin` for custom plugin namespace
527
+
528
+ Use `getPlugin()` when you want full control over the plugin namespace and rule severities instead of the all-in-one `getConfig()`.
529
+
530
+ ```ts
531
+ import eslintReactKit from "@eslint-react/kit";
532
+ import type { RuleDefinition } from "@eslint-react/kit";
533
+
534
+ function noForwardRef(): RuleDefinition {
535
+ return (context, { is }) => ({
536
+ CallExpression(node) {
537
+ if (is.forwardRefCall(node)) {
538
+ context.report({ node, message: "forwardRef is deprecated in React 19." });
539
+ }
540
+ },
541
+ });
542
+ }
543
+
544
+ function requireReact19(): RuleDefinition {
545
+ return (context, { settings }) => ({
546
+ Program(program) {
547
+ if (!settings.version.startsWith("19.")) {
548
+ context.report({
549
+ node: program,
550
+ message: `This project requires React 19, but detected version ${settings.version}.`,
551
+ });
552
+ }
553
+ },
554
+ });
555
+ }
556
+
557
+ const kit = eslintReactKit()
558
+ .use(noForwardRef)
559
+ .use(requireReact19);
560
+
561
+ // Instead of kit.getConfig(), use kit.getPlugin() for full control:
562
+ const plugin = kit.getPlugin();
563
+
564
+ export default [
565
+ {
566
+ files: ["**/*.{ts,tsx}"],
567
+ plugins: {
568
+ // Choose your own namespace
569
+ "react-custom": plugin,
570
+ },
571
+ rules: {
572
+ // Set individual severities
573
+ "react-custom/no-forward-ref": "error",
574
+ "react-custom/require-react-19": "error",
575
+ },
576
+ },
577
+ ];
578
+ ```
579
+
580
+ <!--## More Examples
476
581
 
477
- Please check the [Rule Recipes](https://beta.eslint-react.xyz/docs/configuration/configure-custom-rules#rule-recipes) in the documentation site.
582
+ 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
@@ -3,7 +3,7 @@ import { ESLintReactSettingsNormalized, RuleFix, RuleFixer, RuleListener, define
3
3
  import { TSESTreeFunction } from "@eslint-react/ast";
4
4
  import { TSESTree } from "@typescript-eslint/utils";
5
5
  import { RuleContext } from "@typescript-eslint/utils/ts-eslint";
6
- import { Linter } from "eslint";
6
+ import { ESLint, Linter } from "eslint";
7
7
 
8
8
  //#region src/index.d.ts
9
9
  interface Collector<T> {
@@ -17,20 +17,13 @@ interface CollectorWithContext<T> extends Collector<T> {
17
17
  all(program: TSESTree.Program): T[];
18
18
  };
19
19
  }
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
- }
27
20
  interface RuleToolkit {
28
21
  collect: {
29
- components(ctx: RuleContext, options?: {
22
+ components(context: RuleContext, options?: {
30
23
  collectDisplayName?: boolean;
31
24
  hint?: bigint;
32
25
  }): CollectorWithContext<core.FunctionComponentSemanticNode>;
33
- hooks(ctx: RuleContext): CollectorWithContext<core.HookSemanticNode>;
26
+ hooks(context: RuleContext): CollectorWithContext<core.HookSemanticNode>;
34
27
  };
35
28
  flag: {
36
29
  component: typeof core.ComponentFlag;
@@ -106,7 +99,15 @@ interface RuleToolkit {
106
99
  };
107
100
  settings: ESLintReactSettingsNormalized;
108
101
  }
109
- declare function eslintReactKit(): KitBuilder;
102
+ type RuleDefinition = (context: RuleContext, toolkit: RuleToolkit) => RuleListener;
103
+ interface Builder {
104
+ getConfig(args?: {
105
+ files?: string[];
106
+ }): Linter.Config;
107
+ getPlugin(): ESLint.Plugin;
108
+ use<F extends (...args: any[]) => RuleDefinition>(factory: F, ...args: Parameters<F>): Builder;
109
+ }
110
+ declare function eslintReactKit(): Builder;
110
111
  declare module "@typescript-eslint/utils/ts-eslint" {
111
112
  interface RuleContext<MessageIds extends string = string, Options extends readonly unknown[] = readonly unknown[]> {
112
113
  report(descriptor: {
@@ -124,4 +125,4 @@ declare module "@typescript-eslint/utils/ts-eslint" {
124
125
  }
125
126
  }
126
127
  //#endregion
127
- export { Collector, CollectorWithContext, KitBuilder, RuleDefinition, type RuleFix, type RuleFixer, type RuleListener, eslintReactKit as default, merge };
128
+ export { Builder, Collector, CollectorWithContext, RuleDefinition, type RuleFix, type RuleFixer, type RuleListener, eslintReactKit as default, merge };
package/dist/index.js CHANGED
@@ -4,15 +4,15 @@ import { kebabCase } from "string-ts";
4
4
 
5
5
  //#region package.json
6
6
  var name = "@eslint-react/kit";
7
- var version = "4.0.1-beta.0";
7
+ var version = "4.0.2-beta.0";
8
8
 
9
9
  //#endregion
10
10
  //#region src/index.ts
11
- function createKit(ctx) {
11
+ function makeRuleToolkit(context) {
12
12
  return {
13
13
  collect: {
14
- components(ctx, options) {
15
- const { api, visitor } = core.getComponentCollector(ctx, options);
14
+ components(context, options) {
15
+ const { api, visitor } = core.getComponentCollector(context, options);
16
16
  return {
17
17
  query: { all(program) {
18
18
  return api.getAllComponents(program);
@@ -20,8 +20,8 @@ function createKit(ctx) {
20
20
  visitor
21
21
  };
22
22
  },
23
- hooks(ctx) {
24
- const { api, visitor } = core.getHookCollector(ctx);
23
+ hooks(context) {
24
+ const { api, visitor } = core.getHookCollector(context);
25
25
  return {
26
26
  query: { all(program) {
27
27
  return api.getAllHooks(program);
@@ -36,45 +36,45 @@ function createKit(ctx) {
36
36
  Default: core.DEFAULT_COMPONENT_DETECTION_HINT
37
37
  } },
38
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),
53
- componentDefinition: (node, hint) => core.isComponentDefinition(ctx, node, hint),
39
+ captureOwnerStack: core.isCaptureOwnerStack(context),
40
+ captureOwnerStackCall: core.isCaptureOwnerStackCall(context),
41
+ childrenCount: core.isChildrenCount(context),
42
+ childrenCountCall: core.isChildrenCountCall(context),
43
+ childrenForEach: core.isChildrenForEach(context),
44
+ childrenForEachCall: core.isChildrenForEachCall(context),
45
+ childrenMap: core.isChildrenMap(context),
46
+ childrenMapCall: core.isChildrenMapCall(context),
47
+ childrenOnly: core.isChildrenOnly(context),
48
+ childrenOnlyCall: core.isChildrenOnlyCall(context),
49
+ childrenToArray: core.isChildrenToArray(context),
50
+ childrenToArrayCall: core.isChildrenToArrayCall(context),
51
+ cloneElement: core.isCloneElement(context),
52
+ cloneElementCall: core.isCloneElementCall(context),
53
+ componentDefinition: (node, hint) => core.isComponentDefinition(context, node, hint),
54
54
  componentName: core.isComponentName,
55
55
  componentNameLoose: core.isComponentNameLoose,
56
- componentWrapperCall: (node) => core.isComponentWrapperCall(ctx, node),
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),
56
+ componentWrapperCall: (node) => core.isComponentWrapperCall(context, node),
57
+ componentWrapperCallback: (node) => core.isComponentWrapperCallback(context, node),
58
+ componentWrapperCallLoose: (node) => core.isComponentWrapperCallLoose(context, node),
59
+ createContext: core.isCreateContext(context),
60
+ createContextCall: core.isCreateContextCall(context),
61
+ createElement: core.isCreateElement(context),
62
+ createElementCall: core.isCreateElementCall(context),
63
+ createRef: core.isCreateRef(context),
64
+ createRefCall: core.isCreateRefCall(context),
65
+ forwardRef: core.isForwardRef(context),
66
+ forwardRefCall: core.isForwardRefCall(context),
67
67
  hook: core.isHook,
68
68
  hookCall: core.isHookCall,
69
69
  hookName: core.isHookName,
70
70
  initializedFromReact: core.isInitializedFromReact,
71
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),
72
+ lazy: core.isLazy(context),
73
+ lazyCall: core.isLazyCall(context),
74
+ memo: core.isMemo(context),
75
+ memoCall: core.isMemoCall(context),
76
+ reactAPI: (api) => core.isReactAPI(api)(context),
77
+ reactAPICall: (api) => core.isReactAPICall(api)(context),
78
78
  useActionStateCall: core.isUseActionStateCall,
79
79
  useCall: core.isUseCall,
80
80
  useCallbackCall: core.isUseCallbackCall,
@@ -99,45 +99,40 @@ function createKit(ctx) {
99
99
  useSyncExternalStoreCall: core.isUseSyncExternalStoreCall,
100
100
  useTransitionCall: core.isUseTransitionCall
101
101
  },
102
- settings: getSettingsFromContext(ctx)
102
+ settings: getSettingsFromContext(context)
103
103
  };
104
104
  }
105
105
  function eslintReactKit() {
106
106
  const idGen = new IdGenerator();
107
- const rules = [];
107
+ const rules = {};
108
108
  const builder = {
109
109
  getConfig({ files = ["**/*.ts", "**/*.tsx"] } = {}) {
110
110
  return {
111
111
  files,
112
- plugins: { [name]: {
113
- meta: {
114
- name,
115
- version
116
- },
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 }) => {
112
+ plugins: { [name]: builder.getPlugin() },
113
+ rules: Object.keys(rules).reduce((acc, name$1) => {
131
114
  acc[`${name}/${name$1}`] = "error";
132
115
  return acc;
133
116
  }, {})
134
117
  };
135
118
  },
136
- use(factory, ...args) {
137
- const name = kebabCase(factory.name === "" ? idGen.next() : factory.name);
138
- rules.push({
139
- name,
140
- make: factory(...args)
119
+ getPlugin() {
120
+ return {
121
+ meta: {
122
+ name,
123
+ version
124
+ },
125
+ rules
126
+ };
127
+ },
128
+ use(make, ...args) {
129
+ const name = kebabCase(make.name === "" ? idGen.next() : make.name);
130
+ Reflect.set(rules, name, {
131
+ meta: {
132
+ fixable: "code",
133
+ hasSuggestions: true
134
+ },
135
+ create: (context) => make(...args)(context, makeRuleToolkit(context))
141
136
  });
142
137
  return builder;
143
138
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eslint-react/kit",
3
- "version": "4.0.1-beta.0",
3
+ "version": "4.0.2-beta.0",
4
4
  "description": "ESLint React's utility module for building custom rules.",
5
5
  "keywords": [
6
6
  "react",
@@ -38,9 +38,9 @@
38
38
  "dependencies": {
39
39
  "@typescript-eslint/utils": "^8.57.2",
40
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"
41
+ "@eslint-react/ast": "4.0.2-beta.0",
42
+ "@eslint-react/core": "4.0.2-beta.0",
43
+ "@eslint-react/shared": "4.0.2-beta.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "eslint": "^10.1.0",