@eslint-react/kit 5.0.0-beta.0 → 5.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 +75 -22
- package/dist/index.d.ts +3 -20
- package/dist/index.js +6 -19
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ ESLint React's toolkit for building custom React rules with JavasSript functions
|
|
|
24
24
|
- [Component: Destructure component props](#component-destructure-component-props)
|
|
25
25
|
- [Hooks: Warn on custom hooks that don't call other hooks](#hooks-warn-on-custom-hooks-that-dont-call-other-hooks)
|
|
26
26
|
- [Multiple Collectors: No component/hook factories](#multiple-collectors-no-componenthook-factories)
|
|
27
|
-
- [Override Config: Using spread syntax with `getConfig`](#
|
|
27
|
+
- [Override Config: Using spread syntax with `getConfig`](#override-config-using-spread-syntax-with-getconfig)
|
|
28
28
|
- [Advanced Config: Using `getPlugin` for custom plugin namespace](#advanced-config-using-getplugin-for-custom-plugin-namespace)
|
|
29
29
|
- [More Examples](#more-examples)
|
|
30
30
|
|
|
@@ -102,7 +102,7 @@ import type { RuleFunction } from "@eslint-react/kit";
|
|
|
102
102
|
type RuleFunction = (ctx: RuleContext, kit: RuleToolkit) => RuleListener;
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
A
|
|
105
|
+
A function that receives the ESLint rule context and the structured `Kit` toolkit, and returns a `RuleListener` (AST visitor object).
|
|
106
106
|
|
|
107
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
108
|
|
|
@@ -119,6 +119,75 @@ function forbidElements({ forbidden }: ForbidElementsOptions): RuleFunction {
|
|
|
119
119
|
}
|
|
120
120
|
```
|
|
121
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
|
+
esslintReactKit().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
|
+
##### Characteristics of Anonymous Rules
|
|
139
|
+
|
|
140
|
+
| Feature | Description |
|
|
141
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
142
|
+
| **Random name** | A unique ULID is generated for each rule on every lint run |
|
|
143
|
+
| **Non-disablable** | Cannot be disabled via `{ rules: { <rule-name>: "off" } }` or `// eslint-disable-next-line <rule-name>` because the name changes each time |
|
|
144
|
+
| **Use case** | Critical checks that must never be bypassed |
|
|
145
|
+
|
|
146
|
+
Anonymous rules are ideal for checks that are **critical to code quality or security** and should never be bypassed via disable comments:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
// This critical security check cannot be easily disabled by developers
|
|
150
|
+
eslintReactKit().use(() => (context, { is }) => ({
|
|
151
|
+
CallExpression(node) {
|
|
152
|
+
// Prevent dangerous API calls that could lead to XSS
|
|
153
|
+
if (is.createElement(node) && isUnsafeArguments(node.arguments)) {
|
|
154
|
+
context.report({
|
|
155
|
+
node,
|
|
156
|
+
message: "Potential XSS vulnerability detected. This issue must be fixed.",
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
}));
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Note:** Since the rule name is random and changes on every ESLint run, developers cannot use standard rules configs or disable comments like:
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
// This will NOT work - the rule name is random!
|
|
167
|
+
{ rules: { "01KNE2WSJ8011D2HXE3A6H717C": "off" } }
|
|
168
|
+
|
|
169
|
+
// This will NOT work - the rule name is random!
|
|
170
|
+
// eslint-disable-next-line 01KNE2WSJ8011D2HXE3A6H717C
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
To disable an anonymous rule, developers must modify the ESLint configuration file directly, which provides an audit trail for policy violations.
|
|
174
|
+
|
|
175
|
+
##### Debugging Anonymous Rules
|
|
176
|
+
|
|
177
|
+
For debugging purposes, you can [set a `displayName`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/displayName#setting_a_displayname) on an anonymous function. The display name will be used as a label in various places. This helps identify the rule, while the actual registered rule name remains a random ULID:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import { defineRule } from "@eslint-react/kit";
|
|
181
|
+
|
|
182
|
+
const myRule = defineRule(() => (context) => ({}));
|
|
183
|
+
|
|
184
|
+
myRule.displayName = "my-rule";
|
|
185
|
+
|
|
186
|
+
eslintReactKit().use(myRule);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
> **Note:** This feature is currently under development and may not be available in the current release.
|
|
190
|
+
|
|
122
191
|
### `Builder`
|
|
123
192
|
|
|
124
193
|
```ts
|
|
@@ -257,21 +326,6 @@ Factory functions (context pre-bound):
|
|
|
257
326
|
| `API` | `(apiName) -> (node) -> boolean` | Factory: creates a predicate for a React API identifier. (context pre-bound) |
|
|
258
327
|
| `APICall` | `(apiName) -> (node) -> boolean` | Factory: creates a predicate for a React API call. (context pre-bound) |
|
|
259
328
|
|
|
260
|
-
Pre-built identifier predicates (context pre-bound):
|
|
261
|
-
|
|
262
|
-
- `captureOwnerStack`
|
|
263
|
-
- `childrenCount`
|
|
264
|
-
- `childrenForEach`
|
|
265
|
-
- `childrenMap`
|
|
266
|
-
- `childrenOnly`
|
|
267
|
-
- `childrenToArray`
|
|
268
|
-
- `cloneElement`
|
|
269
|
-
- `createContext`
|
|
270
|
-
- `createElement`
|
|
271
|
-
- `forwardRef`
|
|
272
|
-
- `memo`
|
|
273
|
-
- `lazy`
|
|
274
|
-
|
|
275
329
|
Pre-built call predicates (context pre-bound):
|
|
276
330
|
|
|
277
331
|
- `captureOwnerStackCall`
|
|
@@ -291,7 +345,6 @@ All React API predicates and factories have `context` pre-bound — no need to p
|
|
|
291
345
|
|
|
292
346
|
```ts
|
|
293
347
|
// Direct check
|
|
294
|
-
is.memo(node);
|
|
295
348
|
is.memoCall(node);
|
|
296
349
|
|
|
297
350
|
// Useful in filter/find
|
|
@@ -304,10 +357,10 @@ isCreateRefCall(node);
|
|
|
304
357
|
|
|
305
358
|
##### Import source
|
|
306
359
|
|
|
307
|
-
| Predicate
|
|
308
|
-
|
|
|
309
|
-
| `
|
|
310
|
-
| `
|
|
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. |
|
|
311
364
|
|
|
312
365
|
---
|
|
313
366
|
|
package/dist/index.d.ts
CHANGED
|
@@ -2362,41 +2362,28 @@ interface RuleToolkit {
|
|
|
2362
2362
|
is: {
|
|
2363
2363
|
API: (api: string) => (node: null | Node) => boolean;
|
|
2364
2364
|
APICall: (api: string) => (node: null | Node) => node is CallExpression;
|
|
2365
|
-
|
|
2365
|
+
APIFromReact: typeof core.isAPIFromReact;
|
|
2366
|
+
APIFromReactNative: typeof core.isAPIFromReactNative;
|
|
2366
2367
|
captureOwnerStackCall: (node: null | Node) => node is CallExpression;
|
|
2367
|
-
childrenCount: (node: null | Node) => boolean;
|
|
2368
2368
|
childrenCountCall: (node: null | Node) => node is CallExpression;
|
|
2369
|
-
childrenForEach: (node: null | Node) => boolean;
|
|
2370
2369
|
childrenForEachCall: (node: null | Node) => node is CallExpression;
|
|
2371
|
-
childrenMap: (node: null | Node) => boolean;
|
|
2372
2370
|
childrenMapCall: (node: null | Node) => node is CallExpression;
|
|
2373
|
-
childrenOnly: (node: null | Node) => boolean;
|
|
2374
2371
|
childrenOnlyCall: (node: null | Node) => node is CallExpression;
|
|
2375
|
-
childrenToArray: (node: null | Node) => boolean;
|
|
2376
2372
|
childrenToArrayCall: (node: null | Node) => node is CallExpression;
|
|
2377
|
-
cloneElement: (node: null | Node) => boolean;
|
|
2378
2373
|
cloneElementCall: (node: null | Node) => node is CallExpression;
|
|
2379
2374
|
componentDecl: (node: TSESTreeFunction, hint: bigint) => boolean;
|
|
2380
2375
|
componentName: typeof core.isFunctionComponentName;
|
|
2381
2376
|
componentNameLoose: typeof core.isFunctionComponentNameLoose;
|
|
2382
2377
|
componentWrapperCall: (node: Node) => boolean;
|
|
2383
2378
|
componentWrapperCallback: (node: Node) => boolean;
|
|
2384
|
-
createContext: (node: null | Node) => boolean;
|
|
2385
2379
|
createContextCall: (node: null | Node) => node is CallExpression;
|
|
2386
|
-
createElement: (node: null | Node) => boolean;
|
|
2387
2380
|
createElementCall: (node: null | Node) => node is CallExpression;
|
|
2388
|
-
createRef: (node: null | Node) => boolean;
|
|
2389
2381
|
createRefCall: (node: null | Node) => node is CallExpression;
|
|
2390
|
-
forwardRef: (node: null | Node) => boolean;
|
|
2391
2382
|
forwardRefCall: (node: null | Node) => node is CallExpression;
|
|
2392
2383
|
hookCall: typeof core.isHookCall;
|
|
2393
2384
|
hookDecl: typeof core.isHookDefinition;
|
|
2394
2385
|
hookName: typeof core.isHookName;
|
|
2395
|
-
initializedFromReact: typeof core.isAPIFromReact;
|
|
2396
|
-
initializedFromReactNative: typeof core.isAPIFromReactNative;
|
|
2397
|
-
lazy: (node: null | Node) => boolean;
|
|
2398
2386
|
lazyCall: (node: null | Node) => node is CallExpression;
|
|
2399
|
-
memo: (node: null | Node) => boolean;
|
|
2400
2387
|
memoCall: (node: null | Node) => node is CallExpression;
|
|
2401
2388
|
useActionStateCall: (node: null | Node) => node is CallExpression;
|
|
2402
2389
|
useCall: (node: null | Node) => node is CallExpression;
|
|
@@ -2425,10 +2412,6 @@ interface RuleToolkit {
|
|
|
2425
2412
|
settings: ESLintReactSettingsNormalized;
|
|
2426
2413
|
}
|
|
2427
2414
|
type RuleFunction = (context: RuleContext, toolkit: RuleToolkit) => RuleListener;
|
|
2428
|
-
/**
|
|
2429
|
-
* @deprecated Use `RuleFunction` instead.
|
|
2430
|
-
*/
|
|
2431
|
-
type RuleDefinition = RuleFunction;
|
|
2432
2415
|
interface Builder {
|
|
2433
2416
|
getConfig(): Linter.Config;
|
|
2434
2417
|
getPlugin(): ESLint.Plugin;
|
|
@@ -2452,4 +2435,4 @@ declare module "@typescript-eslint/utils/ts-eslint" {
|
|
|
2452
2435
|
}
|
|
2453
2436
|
}
|
|
2454
2437
|
//#endregion
|
|
2455
|
-
export { Builder, Collector, CollectorWithContext,
|
|
2438
|
+
export { Builder, Collector, CollectorWithContext, type RuleFix, type RuleFixer, RuleFunction, type RuleListener, build as default, merge };
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as core from "@eslint-react/core";
|
|
2
|
-
import {
|
|
2
|
+
import { defineRuleListener as merge, getSettingsFromContext } from "@eslint-react/shared";
|
|
3
3
|
import { kebabCase } from "string-ts";
|
|
4
|
+
import { ulid } from "ulid";
|
|
4
5
|
|
|
5
6
|
//#region package.json
|
|
6
7
|
var name = "@eslint-react/kit";
|
|
7
|
-
var version = "5.0.
|
|
8
|
+
var version = "5.0.1-beta.0";
|
|
8
9
|
|
|
9
10
|
//#endregion
|
|
10
11
|
//#region src/index.ts
|
|
@@ -38,41 +39,28 @@ function makeRuleToolkit(context) {
|
|
|
38
39
|
is: {
|
|
39
40
|
API: (api) => core.isAPI(api)(context),
|
|
40
41
|
APICall: (api) => core.isAPICall(api)(context),
|
|
41
|
-
|
|
42
|
+
APIFromReact: core.isAPIFromReact,
|
|
43
|
+
APIFromReactNative: core.isAPIFromReactNative,
|
|
42
44
|
captureOwnerStackCall: core.isCaptureOwnerStackCall(context),
|
|
43
|
-
childrenCount: core.isChildrenCount(context),
|
|
44
45
|
childrenCountCall: core.isChildrenCountCall(context),
|
|
45
|
-
childrenForEach: core.isChildrenForEach(context),
|
|
46
46
|
childrenForEachCall: core.isChildrenForEachCall(context),
|
|
47
|
-
childrenMap: core.isChildrenMap(context),
|
|
48
47
|
childrenMapCall: core.isChildrenMapCall(context),
|
|
49
|
-
childrenOnly: core.isChildrenOnly(context),
|
|
50
48
|
childrenOnlyCall: core.isChildrenOnlyCall(context),
|
|
51
|
-
childrenToArray: core.isChildrenToArray(context),
|
|
52
49
|
childrenToArrayCall: core.isChildrenToArrayCall(context),
|
|
53
|
-
cloneElement: core.isCloneElement(context),
|
|
54
50
|
cloneElementCall: core.isCloneElementCall(context),
|
|
55
51
|
componentDecl: (node, hint) => core.isFunctionComponentDefinition(context, node, hint),
|
|
56
52
|
componentName: core.isFunctionComponentName,
|
|
57
53
|
componentNameLoose: core.isFunctionComponentNameLoose,
|
|
58
54
|
componentWrapperCall: (node) => core.isFunctionComponentWrapperCall(context, node),
|
|
59
55
|
componentWrapperCallback: (node) => core.isFunctionComponentWrapperCallback(context, node),
|
|
60
|
-
createContext: core.isCreateContext(context),
|
|
61
56
|
createContextCall: core.isCreateContextCall(context),
|
|
62
|
-
createElement: core.isCreateElement(context),
|
|
63
57
|
createElementCall: core.isCreateElementCall(context),
|
|
64
|
-
createRef: core.isCreateRef(context),
|
|
65
58
|
createRefCall: core.isCreateRefCall(context),
|
|
66
|
-
forwardRef: core.isForwardRef(context),
|
|
67
59
|
forwardRefCall: core.isForwardRefCall(context),
|
|
68
60
|
hookCall: core.isHookCall,
|
|
69
61
|
hookDecl: core.isHookDefinition,
|
|
70
62
|
hookName: core.isHookName,
|
|
71
|
-
initializedFromReact: core.isAPIFromReact,
|
|
72
|
-
initializedFromReactNative: core.isAPIFromReactNative,
|
|
73
|
-
lazy: core.isLazy(context),
|
|
74
63
|
lazyCall: core.isLazyCall(context),
|
|
75
|
-
memo: core.isMemo(context),
|
|
76
64
|
memoCall: core.isMemoCall(context),
|
|
77
65
|
useActionStateCall: core.isUseActionStateCall(context),
|
|
78
66
|
useCall: core.isUseCall(context),
|
|
@@ -102,7 +90,6 @@ function makeRuleToolkit(context) {
|
|
|
102
90
|
};
|
|
103
91
|
}
|
|
104
92
|
function build() {
|
|
105
|
-
const idGen = new IdGenerator();
|
|
106
93
|
const rules = {};
|
|
107
94
|
const builder = {
|
|
108
95
|
getConfig() {
|
|
@@ -126,7 +113,7 @@ function build() {
|
|
|
126
113
|
};
|
|
127
114
|
},
|
|
128
115
|
use(make, ...args) {
|
|
129
|
-
const name =
|
|
116
|
+
const name = make.name === "" ? ulid() : kebabCase(make.name);
|
|
130
117
|
Reflect.set(rules, name, {
|
|
131
118
|
meta: {
|
|
132
119
|
fixable: "code",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eslint-react/kit",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.1-beta.0",
|
|
4
4
|
"description": "ESLint React's utility module for building custom React rules with JavasSript functions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -38,9 +38,10 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@typescript-eslint/utils": "^8.58.0",
|
|
40
40
|
"string-ts": "^2.3.1",
|
|
41
|
-
"
|
|
42
|
-
"@eslint-react/
|
|
43
|
-
"@eslint-react/shared": "5.0.
|
|
41
|
+
"ulid": "^3.0.2",
|
|
42
|
+
"@eslint-react/ast": "5.0.1-beta.0",
|
|
43
|
+
"@eslint-react/shared": "5.0.1-beta.0",
|
|
44
|
+
"@eslint-react/core": "5.0.1-beta.0"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"eslint": "^10.2.0",
|