eslint-plugin-react-x 2.7.4-beta.4 โ 2.7.4-beta.6
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 +79 -0
- package/dist/index.d.ts +16 -16
- package/dist/index.js +86 -86
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -36,6 +36,85 @@ export default defineConfig(
|
|
|
36
36
|
);
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
## JSX Rules
|
|
40
|
+
|
|
41
|
+
> [!NOTE]
|
|
42
|
+
> The `jsx-*` rules check for issues exclusive to JSX syntax, which are absent from standard JavaScript (like handwritten `createElement()` calls).
|
|
43
|
+
|
|
44
|
+
**Key Rules:**
|
|
45
|
+
|
|
46
|
+
- `jsx-dollar` - Prevents unnecessary `$` symbols before `JSX` expressions
|
|
47
|
+
- `jsx-key-before-spread` - Enforces `key` prop placement before spread operators
|
|
48
|
+
- `jsx-no-comment-textnodes` - Prevents comments from rendering as text
|
|
49
|
+
- `jsx-no-duplicate-props` - Disallows duplicate `props` in elements
|
|
50
|
+
- `jsx-no-iife` - Disallows immediately-invoked function expressions in `JSX`
|
|
51
|
+
- `jsx-no-undef` - Disallows undefined variables in `JSX` elements
|
|
52
|
+
- `jsx-shorthand-boolean` - Enforces shorthand for boolean attributes (๐ง Fixable, โ๏ธ Configurable)
|
|
53
|
+
- `jsx-shorthand-fragment` - Enforces `<>` over `<React.Fragment>` (๐ง Fixable, โ๏ธ Configurable)
|
|
54
|
+
- `jsx-uses-react` - Marks `React` as used when `JSX` is present
|
|
55
|
+
- `jsx-uses-vars` - Marks `JSX` element variables as used
|
|
56
|
+
|
|
57
|
+
## Component Rules
|
|
58
|
+
|
|
59
|
+
> [!NOTE]
|
|
60
|
+
> Component rules enforce best practices and prevent common mistakes in `React` component definitions, covering both class and function components.
|
|
61
|
+
|
|
62
|
+
**Key Rule Groups:**
|
|
63
|
+
|
|
64
|
+
**Lifecycle & Deprecated APIs:**
|
|
65
|
+
|
|
66
|
+
- `no-component-will-mount` - Replaces `componentWillMount` with `UNSAFE_componentWillMount` (๐ Codemod, `React` >=16.3.0)
|
|
67
|
+
- `no-component-will-receive-props` - Replaces `componentWillReceiveProps` with `UNSAFE_componentWillReceiveProps` (๐ Codemod)
|
|
68
|
+
- `no-component-will-update` - Replaces `componentWillUpdate` with `UNSAFE_componentWillUpdate` (๐ Codemod)
|
|
69
|
+
- `no-unsafe-component-will-mount` - Warns about `UNSAFE_componentWillMount` usage
|
|
70
|
+
- `no-unsafe-component-will-receive-props` - Warns about `UNSAFE_componentWillReceiveProps` usage
|
|
71
|
+
- `no-unsafe-component-will-update` - Warns about `UNSAFE_componentWillUpdate` usage
|
|
72
|
+
|
|
73
|
+
**React 19 Migrations:**
|
|
74
|
+
|
|
75
|
+
- `no-context-provider` - Replaces `<Context.Provider>` with `<Context>` (๐ Codemod, `React` >=19.0.0)
|
|
76
|
+
- `no-forward-ref` - Replaces `forwardRef` with `ref` as prop (๐ Codemod, `React` >=19.0.0)
|
|
77
|
+
- `no-use-context` - Replaces `useContext` with `use` (๐ Codemod, `React` >=19.0.0)
|
|
78
|
+
|
|
79
|
+
**Component Structure:**
|
|
80
|
+
|
|
81
|
+
- `no-nested-component-definitions` - Prevents defining components inside other components
|
|
82
|
+
- `no-nested-lazy-component-declarations` - Prevents lazy component declarations inside components
|
|
83
|
+
- `no-class-component` - Disallows class components except error boundaries
|
|
84
|
+
|
|
85
|
+
**State Management:**
|
|
86
|
+
|
|
87
|
+
- `no-access-state-in-setstate` - Prevents `this.state` access inside `setState` calls
|
|
88
|
+
- `no-direct-mutation-state` - Prevents direct `this.state` mutation
|
|
89
|
+
- `no-set-state-in-component-did-mount` - Restricts `setState` in `componentDidMount`
|
|
90
|
+
- `no-set-state-in-component-did-update` - Restricts `setState` in `componentDidUpdate`
|
|
91
|
+
- `no-set-state-in-component-will-update` - Restricts `setState` in `componentWillUpdate`
|
|
92
|
+
|
|
93
|
+
**Props & Keys:**
|
|
94
|
+
|
|
95
|
+
- `no-missing-key` - Requires `key` prop in list renderings
|
|
96
|
+
- `no-duplicate-key` - Prevents duplicate `key` values
|
|
97
|
+
- `no-implicit-key` - Prevents implicit `key` spreading (๐งช Experimental)
|
|
98
|
+
- `no-unnecessary-key` - Prevents `key` on non-list elements (๐งช Experimental)
|
|
99
|
+
- `no-array-index-key` - Warns against using array indices as `keys`
|
|
100
|
+
|
|
101
|
+
**Children API:**
|
|
102
|
+
|
|
103
|
+
- `no-children-count` - Disallows `Children.count`
|
|
104
|
+
- `no-children-for-each` - Disallows `Children.forEach`
|
|
105
|
+
- `no-children-map` - Disallows `Children.map`
|
|
106
|
+
- `no-children-only` - Disallows `Children.only`
|
|
107
|
+
- `no-children-to-array` - Disallows `Children.toArray`
|
|
108
|
+
- `no-children-prop` - Disallows passing `children` as prop
|
|
109
|
+
|
|
110
|
+
**Performance & Optimization:**
|
|
111
|
+
|
|
112
|
+
- `no-unstable-context-value` - Prevents unstable values in `Context.Provider`
|
|
113
|
+
- `no-unstable-default-props` - Prevents referential values as default `props`
|
|
114
|
+
- `no-unnecessary-use-callback` - Warns about unnecessary `useCallback` usage (๐งช Experimental)
|
|
115
|
+
- `no-unnecessary-use-memo` - Warns about unnecessary `useMemo` usage (๐งช Experimental)
|
|
116
|
+
- `prefer-use-state-lazy-initialization` - Enforces lazy initialization in `useState`
|
|
117
|
+
|
|
39
118
|
## Rules
|
|
40
119
|
|
|
41
120
|
<https://eslint-react.xyz/docs/rules/overview#x-rules>
|
package/dist/index.d.ts
CHANGED
|
@@ -4,8 +4,8 @@ import * as _eslint_react_shared0 from "@eslint-react/shared";
|
|
|
4
4
|
declare const _default: {
|
|
5
5
|
configs: {
|
|
6
6
|
/**
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
* Disable experimental rules that might be subject to change in the future
|
|
8
|
+
*/
|
|
9
9
|
"disable-experimental": {
|
|
10
10
|
plugins: {};
|
|
11
11
|
name?: string;
|
|
@@ -13,8 +13,8 @@ declare const _default: {
|
|
|
13
13
|
settings?: _eslint_react_shared0.SettingsConfig;
|
|
14
14
|
};
|
|
15
15
|
/**
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
* Disable rules that can be enforced by TypeScript
|
|
17
|
+
*/
|
|
18
18
|
"disable-type-checked": {
|
|
19
19
|
plugins: {};
|
|
20
20
|
name?: string;
|
|
@@ -22,8 +22,8 @@ declare const _default: {
|
|
|
22
22
|
settings?: _eslint_react_shared0.SettingsConfig;
|
|
23
23
|
};
|
|
24
24
|
/**
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
* Enforce rules that are recommended by ESLint React for general purpose React + React DOM projects
|
|
26
|
+
*/
|
|
27
27
|
recommended: {
|
|
28
28
|
plugins: {};
|
|
29
29
|
name?: string;
|
|
@@ -31,8 +31,8 @@ declare const _default: {
|
|
|
31
31
|
settings?: _eslint_react_shared0.SettingsConfig;
|
|
32
32
|
};
|
|
33
33
|
/**
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
* Same as the `recommended-typescript` preset but enables additional rules that require type information
|
|
35
|
+
*/
|
|
36
36
|
"recommended-type-checked": {
|
|
37
37
|
plugins: {};
|
|
38
38
|
name?: string;
|
|
@@ -40,8 +40,8 @@ declare const _default: {
|
|
|
40
40
|
settings?: _eslint_react_shared0.SettingsConfig;
|
|
41
41
|
};
|
|
42
42
|
/**
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
* Same as the `recommended` preset but disables rules that can be enforced by TypeScript
|
|
44
|
+
*/
|
|
45
45
|
"recommended-typescript": {
|
|
46
46
|
plugins: {};
|
|
47
47
|
name?: string;
|
|
@@ -49,8 +49,8 @@ declare const _default: {
|
|
|
49
49
|
settings?: _eslint_react_shared0.SettingsConfig;
|
|
50
50
|
};
|
|
51
51
|
/**
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
* More strict version of the `recommended` preset
|
|
53
|
+
*/
|
|
54
54
|
strict: {
|
|
55
55
|
plugins: {};
|
|
56
56
|
name?: string;
|
|
@@ -58,8 +58,8 @@ declare const _default: {
|
|
|
58
58
|
settings?: _eslint_react_shared0.SettingsConfig;
|
|
59
59
|
};
|
|
60
60
|
/**
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
* Same as the `strict-typescript` preset but enables additional rules that require type information
|
|
62
|
+
*/
|
|
63
63
|
"strict-type-checked": {
|
|
64
64
|
plugins: {};
|
|
65
65
|
name?: string;
|
|
@@ -67,8 +67,8 @@ declare const _default: {
|
|
|
67
67
|
settings?: _eslint_react_shared0.SettingsConfig;
|
|
68
68
|
};
|
|
69
69
|
/**
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
* Same as the `strict` preset but disables rules that can be enforced by TypeScript
|
|
71
|
+
*/
|
|
72
72
|
"strict-typescript": {
|
|
73
73
|
plugins: {};
|
|
74
74
|
name?: string;
|
package/dist/index.js
CHANGED
|
@@ -19,9 +19,9 @@ import { camelCase } from "string-ts";
|
|
|
19
19
|
var __defProp = Object.defineProperty;
|
|
20
20
|
var __exportAll = (all, symbols) => {
|
|
21
21
|
let target = {};
|
|
22
|
-
for (var name
|
|
23
|
-
__defProp(target, name
|
|
24
|
-
get: all[name
|
|
22
|
+
for (var name in all) {
|
|
23
|
+
__defProp(target, name, {
|
|
24
|
+
get: all[name],
|
|
25
25
|
enumerable: true
|
|
26
26
|
});
|
|
27
27
|
}
|
|
@@ -68,7 +68,7 @@ const rules$7 = {
|
|
|
68
68
|
//#endregion
|
|
69
69
|
//#region package.json
|
|
70
70
|
var name$6 = "eslint-plugin-react-x";
|
|
71
|
-
var version = "2.7.4-beta.
|
|
71
|
+
var version = "2.7.4-beta.6";
|
|
72
72
|
|
|
73
73
|
//#endregion
|
|
74
74
|
//#region src/utils/create-rule.ts
|
|
@@ -150,17 +150,17 @@ function getTypeVariants(types) {
|
|
|
150
150
|
} else if (booleans.length === 2) variants.add("boolean");
|
|
151
151
|
const strings = types.filter(isStringType);
|
|
152
152
|
if (strings.length > 0) {
|
|
153
|
-
const evaluated = match(strings).when((types
|
|
153
|
+
const evaluated = match(strings).when((types) => types.every(isTruthyStringType), () => "truthy string").when((types) => types.every(isFalsyStringType), () => "falsy string").otherwise(() => "string");
|
|
154
154
|
variants.add(evaluated);
|
|
155
155
|
}
|
|
156
156
|
const bigints = types.filter(isBigIntType);
|
|
157
157
|
if (bigints.length > 0) {
|
|
158
|
-
const evaluated = match(bigints).when((types
|
|
158
|
+
const evaluated = match(bigints).when((types) => types.every(isTruthyBigIntType), () => "truthy bigint").when((types) => types.every(isFalsyBigIntType), () => "falsy bigint").otherwise(() => "bigint");
|
|
159
159
|
variants.add(evaluated);
|
|
160
160
|
}
|
|
161
161
|
const numbers = types.filter(isNumberType);
|
|
162
162
|
if (numbers.length > 0) {
|
|
163
|
-
const evaluated = match(numbers).when((types
|
|
163
|
+
const evaluated = match(numbers).when((types) => types.every(isTruthyNumberType), () => "truthy number").when((types) => types.every(isFalsyNumberType), () => "falsy number").otherwise(() => "number");
|
|
164
164
|
variants.add(evaluated);
|
|
165
165
|
}
|
|
166
166
|
if (types.some(isEnumType)) variants.add("enum");
|
|
@@ -316,10 +316,10 @@ function create$60(context) {
|
|
|
316
316
|
const props = [];
|
|
317
317
|
for (const attr of node.attributes) {
|
|
318
318
|
if (attr.type === AST_NODE_TYPES.JSXSpreadAttribute) continue;
|
|
319
|
-
const name
|
|
320
|
-
if (typeof name
|
|
321
|
-
if (!props.includes(name
|
|
322
|
-
props.push(name
|
|
319
|
+
const name = attr.name.name;
|
|
320
|
+
if (typeof name !== "string") continue;
|
|
321
|
+
if (!props.includes(name)) {
|
|
322
|
+
props.push(name);
|
|
323
323
|
continue;
|
|
324
324
|
}
|
|
325
325
|
context.report({
|
|
@@ -377,17 +377,17 @@ var jsx_no_undef_default = createRule({
|
|
|
377
377
|
});
|
|
378
378
|
function create$58(context) {
|
|
379
379
|
return { JSXOpeningElement(node) {
|
|
380
|
-
const name
|
|
380
|
+
const name = match(node.name).with({ type: AST_NODE_TYPES.JSXIdentifier }, (n) => n.name).with({
|
|
381
381
|
type: AST_NODE_TYPES.JSXMemberExpression,
|
|
382
382
|
object: { type: AST_NODE_TYPES.JSXIdentifier }
|
|
383
383
|
}, (n) => n.object.name).otherwise(() => null);
|
|
384
|
-
if (name
|
|
385
|
-
if (name
|
|
386
|
-
if (/^[a-z]/u.test(name
|
|
387
|
-
if (findVariable(name
|
|
384
|
+
if (name == null) return;
|
|
385
|
+
if (name === "this") return;
|
|
386
|
+
if (/^[a-z]/u.test(name)) return;
|
|
387
|
+
if (findVariable(name, context.sourceCode.getScope(node)) == null) context.report({
|
|
388
388
|
messageId: "jsxNoUndef",
|
|
389
389
|
node,
|
|
390
|
-
data: { name
|
|
390
|
+
data: { name }
|
|
391
391
|
});
|
|
392
392
|
} };
|
|
393
393
|
}
|
|
@@ -525,12 +525,12 @@ function create$55(context) {
|
|
|
525
525
|
JSXOpeningFragment: handleJsxElement
|
|
526
526
|
};
|
|
527
527
|
}
|
|
528
|
-
function debugReport(context, node, name
|
|
528
|
+
function debugReport(context, node, name) {
|
|
529
529
|
if (process.env["ESLINT_REACT_DEBUG"] !== "1") return;
|
|
530
530
|
context.report({
|
|
531
531
|
messageId: "jsxUsesReact",
|
|
532
532
|
node,
|
|
533
|
-
data: { name
|
|
533
|
+
data: { name }
|
|
534
534
|
});
|
|
535
535
|
}
|
|
536
536
|
|
|
@@ -674,8 +674,8 @@ function getIndexParamPosition(methodName) {
|
|
|
674
674
|
default: return -1;
|
|
675
675
|
}
|
|
676
676
|
}
|
|
677
|
-
function isReactChildrenMethod(name
|
|
678
|
-
return REACT_CHILDREN_METHOD.includes(name
|
|
677
|
+
function isReactChildrenMethod(name) {
|
|
678
|
+
return REACT_CHILDREN_METHOD.includes(name);
|
|
679
679
|
}
|
|
680
680
|
function isUsingReactChildren(context, node) {
|
|
681
681
|
const { importSource = "react" } = coerceSettings(context.settings);
|
|
@@ -691,8 +691,8 @@ function getMapIndexParamName(context, node) {
|
|
|
691
691
|
const { callee } = node;
|
|
692
692
|
if (callee.type !== AST_NODE_TYPES.MemberExpression) return unit;
|
|
693
693
|
if (callee.property.type !== AST_NODE_TYPES.Identifier) return unit;
|
|
694
|
-
const { name
|
|
695
|
-
const indexPosition = getIndexParamPosition(name
|
|
694
|
+
const { name } = callee.property;
|
|
695
|
+
const indexPosition = getIndexParamPosition(name);
|
|
696
696
|
if (indexPosition === -1) return unit;
|
|
697
697
|
const callbackArg = node.arguments[isUsingReactChildren(context, node) ? 1 : 0];
|
|
698
698
|
if (callbackArg == null) return unit;
|
|
@@ -721,7 +721,7 @@ var no_array_index_key_default = createRule({
|
|
|
721
721
|
function create$52(context) {
|
|
722
722
|
const indexParamNames = [];
|
|
723
723
|
function isArrayIndex(node) {
|
|
724
|
-
return node.type === AST_NODE_TYPES.Identifier && indexParamNames.some((name
|
|
724
|
+
return node.type === AST_NODE_TYPES.Identifier && indexParamNames.some((name) => name != null && name === node.name);
|
|
725
725
|
}
|
|
726
726
|
function isCreateOrCloneElementCall(node) {
|
|
727
727
|
return isCreateElementCall(context, node) || isCloneElementCall(context, node);
|
|
@@ -729,7 +729,7 @@ function create$52(context) {
|
|
|
729
729
|
function getReportDescriptors(node) {
|
|
730
730
|
switch (node.type) {
|
|
731
731
|
case AST_NODE_TYPES.Identifier:
|
|
732
|
-
if (indexParamNames.some((name
|
|
732
|
+
if (indexParamNames.some((name) => name != null && name === node.name)) return [{
|
|
733
733
|
messageId: "noArrayIndexKey",
|
|
734
734
|
node
|
|
735
735
|
}];
|
|
@@ -939,12 +939,12 @@ function create$45(context) {
|
|
|
939
939
|
if (!context.sourceCode.text.includes("Component")) return {};
|
|
940
940
|
const { ctx, visitor } = useComponentCollectorLegacy(context);
|
|
941
941
|
return defineRuleListener(visitor, { "Program:exit"(program) {
|
|
942
|
-
for (const { name
|
|
942
|
+
for (const { name = "anonymous", node: component } of ctx.getAllComponents(program)) {
|
|
943
943
|
if (component.body.body.some((m) => isComponentDidCatch(m) || isGetDerivedStateFromError(m))) continue;
|
|
944
944
|
context.report({
|
|
945
945
|
messageId: "noClassComponent",
|
|
946
946
|
node: component,
|
|
947
|
-
data: { name
|
|
947
|
+
data: { name }
|
|
948
948
|
});
|
|
949
949
|
}
|
|
950
950
|
} });
|
|
@@ -1089,8 +1089,8 @@ var no_context_provider_default = createRule({
|
|
|
1089
1089
|
});
|
|
1090
1090
|
function create$40(context) {
|
|
1091
1091
|
if (!context.sourceCode.text.includes("Provider")) return {};
|
|
1092
|
-
const { version
|
|
1093
|
-
if (compare(version
|
|
1092
|
+
const { version } = getSettingsFromContext(context);
|
|
1093
|
+
if (compare(version, "19.0.0", "<")) return {};
|
|
1094
1094
|
return { JSXElement(node) {
|
|
1095
1095
|
const parts = getJsxElementType(context, node).split(".");
|
|
1096
1096
|
const selfName = parts.pop();
|
|
@@ -1280,8 +1280,8 @@ var no_forward_ref_default = createRule({
|
|
|
1280
1280
|
});
|
|
1281
1281
|
function create$35(context) {
|
|
1282
1282
|
if (!context.sourceCode.text.includes("forwardRef")) return {};
|
|
1283
|
-
const { version
|
|
1284
|
-
if (compare(version
|
|
1283
|
+
const { version } = getSettingsFromContext(context);
|
|
1284
|
+
if (compare(version, "19.0.0", "<")) return {};
|
|
1285
1285
|
return { CallExpression(node) {
|
|
1286
1286
|
if (!isForwardRefCall(context, node)) return;
|
|
1287
1287
|
const id = AST.getFunctionId(node);
|
|
@@ -1336,7 +1336,7 @@ function getFix(context, node) {
|
|
|
1336
1336
|
* @returns An array of fixes for the component's signature
|
|
1337
1337
|
*/
|
|
1338
1338
|
function getComponentPropsFixes(context, fixer, node, typeArguments) {
|
|
1339
|
-
const getText = (node
|
|
1339
|
+
const getText = (node) => context.sourceCode.getText(node);
|
|
1340
1340
|
const [arg0, arg1] = node.params;
|
|
1341
1341
|
const [typeArg0, typeArg1] = typeArguments;
|
|
1342
1342
|
if (arg0 == null) return [];
|
|
@@ -1412,7 +1412,7 @@ var no_leaked_conditional_rendering_default = createRule({
|
|
|
1412
1412
|
});
|
|
1413
1413
|
function create$33(context) {
|
|
1414
1414
|
if (!context.sourceCode.text.includes("&&")) return {};
|
|
1415
|
-
const { version
|
|
1415
|
+
const { version } = getSettingsFromContext(context);
|
|
1416
1416
|
const allowedVariants = [
|
|
1417
1417
|
"any",
|
|
1418
1418
|
"boolean",
|
|
@@ -1424,7 +1424,7 @@ function create$33(context) {
|
|
|
1424
1424
|
"truthy boolean",
|
|
1425
1425
|
"truthy number",
|
|
1426
1426
|
"truthy string",
|
|
1427
|
-
...compare(version
|
|
1427
|
+
...compare(version, "18.0.0", "<") ? [] : ["string", "falsy string"]
|
|
1428
1428
|
];
|
|
1429
1429
|
const services = ESLintUtils.getParserServices(context, false);
|
|
1430
1430
|
/**
|
|
@@ -1623,8 +1623,8 @@ function create$30(ctx) {
|
|
|
1623
1623
|
if (inChildrenToArray) return;
|
|
1624
1624
|
if (node.callee.type !== AST_NODE_TYPES.MemberExpression) return;
|
|
1625
1625
|
if (node.callee.property.type !== AST_NODE_TYPES.Identifier) return;
|
|
1626
|
-
const name
|
|
1627
|
-
const idx = name
|
|
1626
|
+
const name = node.callee.property.name;
|
|
1627
|
+
const idx = name === "from" ? 1 : name === "map" ? 0 : -1;
|
|
1628
1628
|
if (idx < 0) return;
|
|
1629
1629
|
const cb = node.arguments[idx];
|
|
1630
1630
|
if (!AST.isFunction(cb)) return;
|
|
@@ -1713,18 +1713,18 @@ function create$28(context) {
|
|
|
1713
1713
|
const isFunctionComponent = (node) => {
|
|
1714
1714
|
return AST.isFunction(node) && fComponents.some((component) => component.node === node);
|
|
1715
1715
|
};
|
|
1716
|
-
const isClassComponent
|
|
1716
|
+
const isClassComponent = (node) => {
|
|
1717
1717
|
return AST.isClass(node) && cComponents.some((component) => component.node === node);
|
|
1718
1718
|
};
|
|
1719
|
-
for (const { name
|
|
1720
|
-
if (name
|
|
1719
|
+
for (const { name, node: component } of fComponents) {
|
|
1720
|
+
if (name == null) continue;
|
|
1721
1721
|
if (isDirectValueOfRenderPropertyLoose(component)) continue;
|
|
1722
1722
|
if (isInsideJSXAttributeValue(component)) {
|
|
1723
1723
|
if (!isDeclaredInRenderPropLoose(component)) context.report({
|
|
1724
1724
|
messageId: "noNestedComponentDefinitions",
|
|
1725
1725
|
node: component,
|
|
1726
1726
|
data: {
|
|
1727
|
-
name
|
|
1727
|
+
name,
|
|
1728
1728
|
suggestion: "Move it to the top level or pass it as a prop."
|
|
1729
1729
|
}
|
|
1730
1730
|
});
|
|
@@ -1735,7 +1735,7 @@ function create$28(context) {
|
|
|
1735
1735
|
messageId: "noNestedComponentDefinitions",
|
|
1736
1736
|
node: component,
|
|
1737
1737
|
data: {
|
|
1738
|
-
name
|
|
1738
|
+
name,
|
|
1739
1739
|
suggestion: "Move it to the top level or pass it as a prop."
|
|
1740
1740
|
}
|
|
1741
1741
|
});
|
|
@@ -1747,7 +1747,7 @@ function create$28(context) {
|
|
|
1747
1747
|
messageId: "noNestedComponentDefinitions",
|
|
1748
1748
|
node: component,
|
|
1749
1749
|
data: {
|
|
1750
|
-
name
|
|
1750
|
+
name,
|
|
1751
1751
|
suggestion: component.parent.type === AST_NODE_TYPES.Property ? "Move it to the top level or pass it as a prop." : "Move it to the top level."
|
|
1752
1752
|
}
|
|
1753
1753
|
});
|
|
@@ -1757,18 +1757,18 @@ function create$28(context) {
|
|
|
1757
1757
|
messageId: "noNestedComponentDefinitions",
|
|
1758
1758
|
node: component,
|
|
1759
1759
|
data: {
|
|
1760
|
-
name
|
|
1760
|
+
name,
|
|
1761
1761
|
suggestion: "Move it to the top level."
|
|
1762
1762
|
}
|
|
1763
1763
|
});
|
|
1764
1764
|
}
|
|
1765
|
-
for (const { name
|
|
1766
|
-
if (AST.findParentNode(component, (n) => isClassComponent
|
|
1765
|
+
for (const { name = "unknown", node: component } of cComponents) {
|
|
1766
|
+
if (AST.findParentNode(component, (n) => isClassComponent(n) || isFunctionComponent(n)) == null) continue;
|
|
1767
1767
|
context.report({
|
|
1768
1768
|
messageId: "noNestedComponentDefinitions",
|
|
1769
1769
|
node: component,
|
|
1770
1770
|
data: {
|
|
1771
|
-
name
|
|
1771
|
+
name,
|
|
1772
1772
|
suggestion: component.parent.type === AST_NODE_TYPES.Property ? "Move it to the top level or pass it as a prop." : "Move it to the top level."
|
|
1773
1773
|
}
|
|
1774
1774
|
});
|
|
@@ -1908,13 +1908,13 @@ function create$25(context) {
|
|
|
1908
1908
|
if (!context.sourceCode.text.includes("shouldComponentUpdate")) return {};
|
|
1909
1909
|
const { ctx, visitor } = useComponentCollectorLegacy(context);
|
|
1910
1910
|
return defineRuleListener(visitor, { "Program:exit"(program) {
|
|
1911
|
-
for (const { name
|
|
1911
|
+
for (const { name = "PureComponent", node: component, flag } of ctx.getAllComponents(program)) {
|
|
1912
1912
|
if ((flag & ComponentFlag.PureComponent) === 0n) continue;
|
|
1913
1913
|
const { body } = component.body;
|
|
1914
1914
|
for (const member of body) if (isShouldComponentUpdate(member)) context.report({
|
|
1915
1915
|
messageId: "noRedundantShouldComponentUpdate",
|
|
1916
1916
|
node: member,
|
|
1917
|
-
data: { componentName: name
|
|
1917
|
+
data: { componentName: name }
|
|
1918
1918
|
});
|
|
1919
1919
|
}
|
|
1920
1920
|
} });
|
|
@@ -2326,16 +2326,16 @@ var no_unnecessary_use_prefix_default = createRule({
|
|
|
2326
2326
|
function create$17(context) {
|
|
2327
2327
|
const { ctx, visitor } = useHookCollector(context);
|
|
2328
2328
|
return defineRuleListener(visitor, { "Program:exit"(program) {
|
|
2329
|
-
for (const { id, name
|
|
2329
|
+
for (const { id, name, node, hookCalls } of ctx.getAllHooks(program)) {
|
|
2330
2330
|
if (hookCalls.length > 0) continue;
|
|
2331
2331
|
if (AST.isFunctionEmpty(node)) continue;
|
|
2332
|
-
if (WELL_KNOWN_HOOKS.includes(name
|
|
2332
|
+
if (WELL_KNOWN_HOOKS.includes(name)) continue;
|
|
2333
2333
|
if (containsUseComments(context, node)) continue;
|
|
2334
2334
|
if (AST.findParentNode(node, AST.isViMockCallback) != null) continue;
|
|
2335
2335
|
context.report({
|
|
2336
2336
|
messageId: "noUnnecessaryUsePrefix",
|
|
2337
2337
|
node: id ?? node,
|
|
2338
|
-
data: { name
|
|
2338
|
+
data: { name }
|
|
2339
2339
|
});
|
|
2340
2340
|
}
|
|
2341
2341
|
} });
|
|
@@ -2365,8 +2365,8 @@ function create$16(context) {
|
|
|
2365
2365
|
if (ref.name.toLowerCase().startsWith("prev")) return;
|
|
2366
2366
|
const effects = /* @__PURE__ */ new Set();
|
|
2367
2367
|
let globalUsages = 0;
|
|
2368
|
-
for (const { identifier, init
|
|
2369
|
-
if (init
|
|
2368
|
+
for (const { identifier, init } of ref.references) {
|
|
2369
|
+
if (init != null) continue;
|
|
2370
2370
|
const effect = AST.findParentNode(identifier, isUseEffectLikeCall);
|
|
2371
2371
|
if (effect == null) globalUsages++;
|
|
2372
2372
|
else effects.add(effect);
|
|
@@ -2478,8 +2478,8 @@ var no_unstable_context_value_default = createRule({
|
|
|
2478
2478
|
defaultOptions: []
|
|
2479
2479
|
});
|
|
2480
2480
|
function create$12(context) {
|
|
2481
|
-
const { version
|
|
2482
|
-
const isReact18OrBelow = compare(version
|
|
2481
|
+
const { version } = getSettingsFromContext(context);
|
|
2482
|
+
const isReact18OrBelow = compare(version, "19.0.0", "<");
|
|
2483
2483
|
const { ctx, visitor } = useComponentCollector(context);
|
|
2484
2484
|
const constructions = /* @__PURE__ */ new WeakMap();
|
|
2485
2485
|
return defineRuleListener(visitor, {
|
|
@@ -2489,7 +2489,7 @@ function create$12(context) {
|
|
|
2489
2489
|
if (!isContextName(selfName, isReact18OrBelow)) return;
|
|
2490
2490
|
const functionEntry = ctx.getCurrentEntry();
|
|
2491
2491
|
if (functionEntry == null) return;
|
|
2492
|
-
const attribute = node.attributes.find((attribute
|
|
2492
|
+
const attribute = node.attributes.find((attribute) => attribute.type === AST_NODE_TYPES.JSXAttribute && attribute.name.name === "value");
|
|
2493
2493
|
if (attribute == null || !("value" in attribute)) return;
|
|
2494
2494
|
const value = attribute.value;
|
|
2495
2495
|
if (value?.type !== AST_NODE_TYPES.JSXExpressionContainer) return;
|
|
@@ -2515,9 +2515,9 @@ function create$12(context) {
|
|
|
2515
2515
|
}
|
|
2516
2516
|
});
|
|
2517
2517
|
}
|
|
2518
|
-
function isContextName(name
|
|
2519
|
-
if (name
|
|
2520
|
-
if (!isReact18OrBelow) return name
|
|
2518
|
+
function isContextName(name, isReact18OrBelow) {
|
|
2519
|
+
if (name === "Provider") return true;
|
|
2520
|
+
if (!isReact18OrBelow) return name.endsWith("Context") || name.endsWith("CONTEXT");
|
|
2521
2521
|
return false;
|
|
2522
2522
|
}
|
|
2523
2523
|
|
|
@@ -2568,8 +2568,8 @@ function create$11(context, [options]) {
|
|
|
2568
2568
|
const { params } = component;
|
|
2569
2569
|
const [props] = params;
|
|
2570
2570
|
if (props == null) continue;
|
|
2571
|
-
const properties = match(props).with({ type: AST_NODE_TYPES.ObjectPattern }, ({ properties
|
|
2572
|
-
return declarators.get(component)?.filter((d) => d.init.name === name
|
|
2571
|
+
const properties = match(props).with({ type: AST_NODE_TYPES.ObjectPattern }, ({ properties }) => properties).with({ type: AST_NODE_TYPES.Identifier }, ({ name }) => {
|
|
2572
|
+
return declarators.get(component)?.filter((d) => d.init.name === name).flatMap((d) => d.id.properties) ?? [];
|
|
2573
2573
|
}).otherwise(() => []);
|
|
2574
2574
|
for (const prop of properties) {
|
|
2575
2575
|
if (prop.type !== AST_NODE_TYPES.Property || prop.value.type !== AST_NODE_TYPES.AssignmentPattern) continue;
|
|
@@ -2964,8 +2964,8 @@ var no_use_context_default = createRule({
|
|
|
2964
2964
|
});
|
|
2965
2965
|
function create$7(context) {
|
|
2966
2966
|
if (!context.sourceCode.text.includes("useContext")) return {};
|
|
2967
|
-
const settings
|
|
2968
|
-
if (compare(settings
|
|
2967
|
+
const settings = getSettingsFromContext(context);
|
|
2968
|
+
if (compare(settings.version, "19.0.0", "<")) return {};
|
|
2969
2969
|
const hookCalls = /* @__PURE__ */ new Set();
|
|
2970
2970
|
return {
|
|
2971
2971
|
CallExpression(node) {
|
|
@@ -2973,7 +2973,7 @@ function create$7(context) {
|
|
|
2973
2973
|
hookCalls.add(node);
|
|
2974
2974
|
},
|
|
2975
2975
|
ImportDeclaration(node) {
|
|
2976
|
-
if (node.source.value !== settings
|
|
2976
|
+
if (node.source.value !== settings.importSource) return;
|
|
2977
2977
|
const isUseImported = node.specifiers.some(isMatching({ local: {
|
|
2978
2978
|
type: AST_NODE_TYPES.Identifier,
|
|
2979
2979
|
name: "use"
|
|
@@ -3095,21 +3095,21 @@ function create$5(context, [option]) {
|
|
|
3095
3095
|
/**
|
|
3096
3096
|
* Check if a fragment node is useless and should be reported
|
|
3097
3097
|
*/
|
|
3098
|
-
function checkNode(context
|
|
3099
|
-
if (node.type === AST_NODE_TYPES.JSXElement && getJsxAttribute(context
|
|
3100
|
-
if (isJsxHostElement(context
|
|
3098
|
+
function checkNode(context, node) {
|
|
3099
|
+
if (node.type === AST_NODE_TYPES.JSXElement && getJsxAttribute(context, node)("key") != null) return;
|
|
3100
|
+
if (isJsxHostElement(context, node.parent)) context.report({
|
|
3101
3101
|
messageId: "noUselessFragment",
|
|
3102
3102
|
node,
|
|
3103
3103
|
data: { reason: "placed inside a host component" },
|
|
3104
|
-
fix: getFix
|
|
3104
|
+
fix: getFix(context, node)
|
|
3105
3105
|
});
|
|
3106
3106
|
if (node.children.length === 0) {
|
|
3107
3107
|
if (allowEmptyFragment) return;
|
|
3108
|
-
context
|
|
3108
|
+
context.report({
|
|
3109
3109
|
messageId: "noUselessFragment",
|
|
3110
3110
|
node,
|
|
3111
3111
|
data: { reason: "contains less than two children" },
|
|
3112
|
-
fix: getFix
|
|
3112
|
+
fix: getFix(context, node)
|
|
3113
3113
|
});
|
|
3114
3114
|
return;
|
|
3115
3115
|
}
|
|
@@ -3117,45 +3117,45 @@ function create$5(context, [option]) {
|
|
|
3117
3117
|
switch (true) {
|
|
3118
3118
|
case allowExpressions && !isChildElement && node.children.length === 1 && isJsxText(node.children.at(0)): return;
|
|
3119
3119
|
case !allowExpressions && isChildElement:
|
|
3120
|
-
context
|
|
3120
|
+
context.report({
|
|
3121
3121
|
messageId: "noUselessFragment",
|
|
3122
3122
|
node,
|
|
3123
3123
|
data: { reason: "contains less than two children" },
|
|
3124
|
-
fix: getFix
|
|
3124
|
+
fix: getFix(context, node)
|
|
3125
3125
|
});
|
|
3126
3126
|
return;
|
|
3127
3127
|
case !allowExpressions && !isChildElement && node.children.length === 1:
|
|
3128
|
-
context
|
|
3128
|
+
context.report({
|
|
3129
3129
|
messageId: "noUselessFragment",
|
|
3130
3130
|
node,
|
|
3131
3131
|
data: { reason: "contains less than two children" },
|
|
3132
|
-
fix: getFix
|
|
3132
|
+
fix: getFix(context, node)
|
|
3133
3133
|
});
|
|
3134
3134
|
return;
|
|
3135
3135
|
}
|
|
3136
3136
|
const nonPaddingChildren = node.children.filter((child) => !isPaddingSpaces(child));
|
|
3137
3137
|
const firstNonPaddingChild = nonPaddingChildren.at(0);
|
|
3138
|
-
if (nonPaddingChildren.length === 0 || nonPaddingChildren.length === 1 && firstNonPaddingChild?.type !== AST_NODE_TYPES.JSXExpressionContainer) context
|
|
3138
|
+
if (nonPaddingChildren.length === 0 || nonPaddingChildren.length === 1 && firstNonPaddingChild?.type !== AST_NODE_TYPES.JSXExpressionContainer) context.report({
|
|
3139
3139
|
messageId: "noUselessFragment",
|
|
3140
3140
|
node,
|
|
3141
3141
|
data: { reason: "contains less than two children" },
|
|
3142
|
-
fix: getFix
|
|
3142
|
+
fix: getFix(context, node)
|
|
3143
3143
|
});
|
|
3144
3144
|
}
|
|
3145
|
-
function getFix
|
|
3146
|
-
if (!canFix
|
|
3145
|
+
function getFix(context, node) {
|
|
3146
|
+
if (!canFix(context, node)) return null;
|
|
3147
3147
|
return (fixer) => {
|
|
3148
3148
|
const opener = node.type === AST_NODE_TYPES.JSXFragment ? node.openingFragment : node.openingElement;
|
|
3149
3149
|
const closer = node.type === AST_NODE_TYPES.JSXFragment ? node.closingFragment : node.closingElement;
|
|
3150
|
-
const childrenText = opener.type === AST_NODE_TYPES.JSXOpeningElement && opener.selfClosing ? "" : context
|
|
3150
|
+
const childrenText = opener.type === AST_NODE_TYPES.JSXOpeningElement && opener.selfClosing ? "" : context.sourceCode.getText().slice(opener.range[1], closer?.range[0]);
|
|
3151
3151
|
return fixer.replaceText(node, trimLikeReact(childrenText));
|
|
3152
3152
|
};
|
|
3153
3153
|
}
|
|
3154
3154
|
/**
|
|
3155
3155
|
* Check if it's safe to automatically fix the fragment
|
|
3156
3156
|
*/
|
|
3157
|
-
function canFix
|
|
3158
|
-
if (node.parent.type === AST_NODE_TYPES.JSXElement || node.parent.type === AST_NODE_TYPES.JSXFragment) return isJsxHostElement(context
|
|
3157
|
+
function canFix(context, node) {
|
|
3158
|
+
if (node.parent.type === AST_NODE_TYPES.JSXElement || node.parent.type === AST_NODE_TYPES.JSXFragment) return isJsxHostElement(context, node.parent);
|
|
3159
3159
|
if (node.children.length === 0) return false;
|
|
3160
3160
|
return !node.children.some((child) => isJsxText(child) && !isWhiteSpace(child) || AST.is(AST_NODE_TYPES.JSXExpressionContainer)(child));
|
|
3161
3161
|
}
|
|
@@ -3217,7 +3217,7 @@ function create$4(context) {
|
|
|
3217
3217
|
const propName = props.name;
|
|
3218
3218
|
const propReferences = context.sourceCode.getScope(component.node).variables.find((v) => v.name === propName)?.references ?? [];
|
|
3219
3219
|
for (const ref of propReferences) {
|
|
3220
|
-
const { name
|
|
3220
|
+
const { name, parent } = ref.identifier;
|
|
3221
3221
|
if (parent.type !== AST_NODE_TYPES.MemberExpression) continue;
|
|
3222
3222
|
context.report({
|
|
3223
3223
|
messageId: "preferDestructuringAssignment",
|
|
@@ -3432,17 +3432,17 @@ function create(context, [option]) {
|
|
|
3432
3432
|
else if (node.name.type === AST_NODE_TYPES.JSXNamespacedName) nodeName = node.name.name.name;
|
|
3433
3433
|
for (const attr of node.attributes) {
|
|
3434
3434
|
if (attr.type === AST_NODE_TYPES.JSXSpreadAttribute) continue;
|
|
3435
|
-
const name
|
|
3436
|
-
if (typeof name
|
|
3435
|
+
const name = attr.name.name;
|
|
3436
|
+
if (typeof name !== "string") continue;
|
|
3437
3437
|
for (const forbiddenPropItem of forbid) {
|
|
3438
3438
|
if (typeof forbiddenPropItem !== "string" && nodeName != null) {
|
|
3439
3439
|
if ("excludedNodes" in forbiddenPropItem && forbiddenPropItem.excludedNodes.includes(nodeName)) continue;
|
|
3440
3440
|
if ("includedNodes" in forbiddenPropItem && !forbiddenPropItem.includedNodes.includes(nodeName)) continue;
|
|
3441
3441
|
}
|
|
3442
|
-
if (toRegExp(typeof forbiddenPropItem === "string" ? forbiddenPropItem : forbiddenPropItem.prop).test(name
|
|
3442
|
+
if (toRegExp(typeof forbiddenPropItem === "string" ? forbiddenPropItem : forbiddenPropItem.prop).test(name)) context.report({
|
|
3443
3443
|
messageId,
|
|
3444
3444
|
node: attr,
|
|
3445
|
-
data: { name
|
|
3445
|
+
data: { name }
|
|
3446
3446
|
});
|
|
3447
3447
|
}
|
|
3448
3448
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-react-x",
|
|
3
|
-
"version": "2.7.4-beta.
|
|
3
|
+
"version": "2.7.4-beta.6",
|
|
4
4
|
"description": "A set of composable ESLint rules for for libraries and frameworks that use React as a UI runtime.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -46,16 +46,16 @@
|
|
|
46
46
|
"string-ts": "^2.3.1",
|
|
47
47
|
"ts-api-utils": "^2.4.0",
|
|
48
48
|
"ts-pattern": "^5.9.0",
|
|
49
|
-
"@eslint-react/ast": "2.7.4-beta.
|
|
50
|
-
"@eslint-react/
|
|
51
|
-
"@eslint-react/
|
|
52
|
-
"@eslint-react/
|
|
53
|
-
"@eslint-react/
|
|
49
|
+
"@eslint-react/ast": "2.7.4-beta.6",
|
|
50
|
+
"@eslint-react/core": "2.7.4-beta.6",
|
|
51
|
+
"@eslint-react/eff": "2.7.4-beta.6",
|
|
52
|
+
"@eslint-react/var": "2.7.4-beta.6",
|
|
53
|
+
"@eslint-react/shared": "2.7.4-beta.6"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/react": "^19.2.9",
|
|
57
57
|
"@types/react-dom": "^19.2.3",
|
|
58
|
-
"tsdown": "^0.20.
|
|
58
|
+
"tsdown": "^0.20.1",
|
|
59
59
|
"@local/configs": "0.0.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|