eslint-plugin-react-x 5.18.0 → 5.18.2
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/dist/index.js +97 -40
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -145,7 +145,7 @@ const rules$6 = {
|
|
|
145
145
|
//#endregion
|
|
146
146
|
//#region package.json
|
|
147
147
|
var name$6 = "eslint-plugin-react-x";
|
|
148
|
-
var version = "5.18.
|
|
148
|
+
var version = "5.18.2";
|
|
149
149
|
|
|
150
150
|
//#endregion
|
|
151
151
|
//#region src/utils/create-rule.ts
|
|
@@ -171,27 +171,6 @@ var error_boundaries_default = createRule({
|
|
|
171
171
|
create: create$50,
|
|
172
172
|
defaultOptions: []
|
|
173
173
|
});
|
|
174
|
-
/**
|
|
175
|
-
* Finds the nearest TryStatement whose `try` block (not catch/finally)
|
|
176
|
-
* encloses the given node.
|
|
177
|
-
* @param node The node to check.
|
|
178
|
-
* @returns The enclosing TryStatement, or null if none is found.
|
|
179
|
-
*/
|
|
180
|
-
function getEnclosingTryBlock(node) {
|
|
181
|
-
let current = node.parent;
|
|
182
|
-
while (current != null) {
|
|
183
|
-
if (current.type === AST_NODE_TYPES.TryStatement) {
|
|
184
|
-
let n = node;
|
|
185
|
-
while (n != null && n !== current) {
|
|
186
|
-
if (n === current.block) return current;
|
|
187
|
-
n = n.parent;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
if (current.type === AST_NODE_TYPES.Program) return null;
|
|
191
|
-
current = current.parent;
|
|
192
|
-
}
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
174
|
function create$50(context) {
|
|
196
175
|
if (!context.sourceCode.text.includes("try")) return {};
|
|
197
176
|
const hint = core.JsxDetectionHint.DoNotIncludeJsxWithNullValue | core.JsxDetectionHint.DoNotIncludeJsxWithNumberValue | core.JsxDetectionHint.DoNotIncludeJsxWithBigIntValue | core.JsxDetectionHint.DoNotIncludeJsxWithStringValue | core.JsxDetectionHint.DoNotIncludeJsxWithBooleanValue | core.JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue | core.JsxDetectionHint.DoNotIncludeJsxWithEmptyArrayValue;
|
|
@@ -209,7 +188,7 @@ function create$50(context) {
|
|
|
209
188
|
const hooks = hc.api.getAllHooks(node);
|
|
210
189
|
const funcs = [...comps, ...hooks];
|
|
211
190
|
for (const call of useCalls) {
|
|
212
|
-
const stmt =
|
|
191
|
+
const stmt = Traverse.findEnclosingTryBlock(call);
|
|
213
192
|
const func = Traverse.findParent(stmt, (n) => funcs.some((f) => f.node === n));
|
|
214
193
|
if (stmt != null && func != null && !reported.has(stmt)) {
|
|
215
194
|
context.report({
|
|
@@ -222,7 +201,7 @@ function create$50(context) {
|
|
|
222
201
|
for (const { rets } of funcs) for (const ret of rets) {
|
|
223
202
|
if (ret == null) continue;
|
|
224
203
|
if (!core.isJsxLike(context, ret, hint)) continue;
|
|
225
|
-
const stmt =
|
|
204
|
+
const stmt = Traverse.findEnclosingTryBlock(ret);
|
|
226
205
|
if (stmt != null && !reported.has(stmt)) {
|
|
227
206
|
context.report({
|
|
228
207
|
messageId: "tryCatchWithJsx",
|
|
@@ -3418,21 +3397,79 @@ function create$23(context) {
|
|
|
3418
3397
|
//#endregion
|
|
3419
3398
|
//#region src/rules/no-nested-component-definitions/lib.ts
|
|
3420
3399
|
/**
|
|
3421
|
-
*
|
|
3422
|
-
*
|
|
3423
|
-
*
|
|
3400
|
+
* Well-known component wrapper function names, matched by exact or `.`-suffixed fully
|
|
3401
|
+
* qualified name (e.g. `memo`, `React.memo`, `mobx.observer`, `connect` from react-redux,
|
|
3402
|
+
* or Relay's `create*Container` helpers). Only wrappers whose argument is a render function
|
|
3403
|
+
* are listed; wrappers taking a component identifier (`styled`, `motion`) or a loader
|
|
3404
|
+
* (`lazy`, `dynamic`) are irrelevant for name resolution.
|
|
3424
3405
|
*/
|
|
3425
|
-
|
|
3426
|
-
|
|
3406
|
+
const WELL_KNOWN_COMPONENT_WRAPPERS = [
|
|
3407
|
+
"connect",
|
|
3408
|
+
"createFragmentContainer",
|
|
3409
|
+
"createPaginationContainer",
|
|
3410
|
+
"createRefetchContainer",
|
|
3411
|
+
"forwardRef",
|
|
3412
|
+
"graphql",
|
|
3413
|
+
"memo",
|
|
3414
|
+
"observer",
|
|
3415
|
+
"useCallback"
|
|
3416
|
+
];
|
|
3417
|
+
/** Matches the HOC naming convention shared by recompose, react-router v5, Formik and custom HOCs (e.g. `withProps`, `withRouter`, `withFormik`, `withAuth`). */
|
|
3418
|
+
const RE_HOC_WRAPPER_NAME = /^with[A-Z]/;
|
|
3419
|
+
/**
|
|
3420
|
+
* Check if a call expression is a well-known component wrapper call.
|
|
3421
|
+
* Only calls whose callee's fully qualified name is (or ends with) a well-known wrapper
|
|
3422
|
+
* name (e.g. `memo`, `React.memo`, `React.useCallback`, `mobx.observer`) are treated as
|
|
3423
|
+
* wrappers; anything else, including member calls on data objects (e.g. `items.map`,
|
|
3424
|
+
* `Array.from`), is not, so array method callbacks are never mistaken for wrapped components.
|
|
3425
|
+
* Curried wrappers like `connect(...)(Component)` or `withFormik(...)(Component)` are
|
|
3426
|
+
* recognized by unwrapping nested callee call expressions.
|
|
3427
|
+
* @param context The rule context
|
|
3428
|
+
* @param node The call expression to check
|
|
3429
|
+
* @returns `true` if the call is a well-known component wrapper call
|
|
3430
|
+
*/
|
|
3431
|
+
function isWellKnownComponentWrapperCall(context, node) {
|
|
3432
|
+
let callee = Extract.unwrap(node.callee);
|
|
3433
|
+
while (callee.type === AST_NODE_TYPES.CallExpression) callee = Extract.unwrap(callee.callee);
|
|
3434
|
+
const name = Extract.getFullyQualifiedName(callee, (n) => context.sourceCode.getText(n));
|
|
3435
|
+
const baseName = name.slice(name.lastIndexOf(".") + 1);
|
|
3436
|
+
return RE_HOC_WRAPPER_NAME.test(baseName) || WELL_KNOWN_COMPONENT_WRAPPERS.some((wrapper) => name === wrapper || name.endsWith(`.${wrapper}`));
|
|
3427
3437
|
}
|
|
3428
3438
|
/**
|
|
3429
|
-
* Check
|
|
3430
|
-
*
|
|
3431
|
-
* @param
|
|
3432
|
-
* @
|
|
3439
|
+
* Check if a call expression is a component wrapper call for name resolution purposes.
|
|
3440
|
+
* @param context The rule context
|
|
3441
|
+
* @param call The call expression to check
|
|
3442
|
+
* @param arg The function node passed to the call
|
|
3443
|
+
* @returns `true` if the call is a component wrapper call
|
|
3433
3444
|
*/
|
|
3434
|
-
function
|
|
3435
|
-
|
|
3445
|
+
function isComponentWrapperCall(context, call, arg) {
|
|
3446
|
+
if (Extract.unwrap(call.callee) === arg) return false;
|
|
3447
|
+
return isWellKnownComponentWrapperCall(context, call);
|
|
3448
|
+
}
|
|
3449
|
+
/**
|
|
3450
|
+
* Resolve the name a function is bound to through a chain of wrapping call expressions,
|
|
3451
|
+
* e.g. `const Component = useCallback(() => <div />, [])` resolves to `Component`.
|
|
3452
|
+
* @param context The rule context
|
|
3453
|
+
* @param node The function node to resolve the bound name for
|
|
3454
|
+
* @returns The bound name if the call chain ends at a variable declarator with an identifier, `null` otherwise
|
|
3455
|
+
*/
|
|
3456
|
+
function getWrapperCallBoundName(context, node) {
|
|
3457
|
+
let current = node;
|
|
3458
|
+
let parent = current.parent;
|
|
3459
|
+
while (Check.isTypeExpression(parent)) {
|
|
3460
|
+
current = parent;
|
|
3461
|
+
parent = parent.parent;
|
|
3462
|
+
}
|
|
3463
|
+
while (parent.type === AST_NODE_TYPES.CallExpression && isComponentWrapperCall(context, parent, current)) {
|
|
3464
|
+
current = parent;
|
|
3465
|
+
parent = parent.parent;
|
|
3466
|
+
while (Check.isTypeExpression(parent)) {
|
|
3467
|
+
current = parent;
|
|
3468
|
+
parent = parent.parent;
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
if (parent.type !== AST_NODE_TYPES.VariableDeclarator || parent.id.type !== AST_NODE_TYPES.Identifier) return null;
|
|
3472
|
+
return parent.id.name;
|
|
3436
3473
|
}
|
|
3437
3474
|
/**
|
|
3438
3475
|
* Determine whether the node is inside `createElement`'s props argument
|
|
@@ -3447,6 +3484,23 @@ function isInsideCreateElementProps(context, node) {
|
|
|
3447
3484
|
if (prop == null) return false;
|
|
3448
3485
|
return prop === call.arguments[1];
|
|
3449
3486
|
}
|
|
3487
|
+
/**
|
|
3488
|
+
* Determine whether the node is inside JSX attribute value
|
|
3489
|
+
* @param node The AST node to check
|
|
3490
|
+
* @returns `true` if the node is inside JSX attribute value
|
|
3491
|
+
*/
|
|
3492
|
+
function isInsideJSXAttributeValue(node) {
|
|
3493
|
+
return node.parent.type === AST_NODE_TYPES.JSXAttribute || findParentAttribute(node, (n) => n.value?.type === AST_NODE_TYPES.JSXExpressionContainer) != null;
|
|
3494
|
+
}
|
|
3495
|
+
/**
|
|
3496
|
+
* Check whether a given node is declared inside a class component's render block
|
|
3497
|
+
* Ex: class C extends React.Component { render() { const Nested = () => <div />; } }
|
|
3498
|
+
* @param node The AST node being checked
|
|
3499
|
+
* @returns `true` if the node is inside a class component's render block
|
|
3500
|
+
*/
|
|
3501
|
+
function isInsideRenderMethod(node) {
|
|
3502
|
+
return Traverse.findParent(node, (n) => core.isRenderMethodLike(n) && core.isClassComponent(n.parent.parent)) != null;
|
|
3503
|
+
}
|
|
3450
3504
|
|
|
3451
3505
|
//#endregion
|
|
3452
3506
|
//#region src/rules/no-nested-component-definitions/no-nested-component-definitions.ts
|
|
@@ -3463,21 +3517,24 @@ var no_nested_component_definitions_default = createRule({
|
|
|
3463
3517
|
defaultOptions: []
|
|
3464
3518
|
});
|
|
3465
3519
|
function create$22(context) {
|
|
3466
|
-
const hint = core.FunctionComponentDetectionHint.DoNotIncludeJsxWithNumberValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithBooleanValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithNullValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithStringValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithUndefinedValue | core.FunctionComponentDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx | core.FunctionComponentDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayPatternElement | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayExpressionElement | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback;
|
|
3520
|
+
const hint = core.FunctionComponentDetectionHint.DoNotIncludeJsxWithNumberValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithBooleanValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithNullValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithStringValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithUndefinedValue | core.FunctionComponentDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx | core.FunctionComponentDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayPatternElement | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayExpressionElement | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback;
|
|
3467
3521
|
const fc = core.getFunctionComponentCollector(context, { hint });
|
|
3468
3522
|
const cc = core.getClassComponentCollector(context);
|
|
3469
3523
|
return merge(fc.visitor, cc.visitor, { "Program:exit"(program) {
|
|
3470
3524
|
const fComponents = [...fc.api.getAllComponents(program)];
|
|
3471
3525
|
const cComponents = [...cc.api.getAllComponents(program)];
|
|
3526
|
+
const fComponentNodes = new Set(fComponents.map((c) => c.node));
|
|
3527
|
+
const cComponentNodes = new Set(cComponents.map((c) => c.node));
|
|
3472
3528
|
function findEnclosingComponent(node) {
|
|
3473
3529
|
return Traverse.findParent(node, (n) => {
|
|
3474
|
-
if (Check.isFunction(n)) return
|
|
3475
|
-
if (Check.isClass(n)) return
|
|
3530
|
+
if (Check.isFunction(n)) return fComponentNodes.has(n);
|
|
3531
|
+
if (Check.isClass(n)) return cComponentNodes.has(n);
|
|
3476
3532
|
return false;
|
|
3477
3533
|
});
|
|
3478
3534
|
}
|
|
3479
|
-
for (
|
|
3480
|
-
|
|
3535
|
+
for (let { name, node: component } of fComponents) {
|
|
3536
|
+
name ??= getWrapperCallBoundName(context, component);
|
|
3537
|
+
if (name == null || !core.isFunctionComponentNameLoose(name)) continue;
|
|
3481
3538
|
if (isInsideJSXAttributeValue(component)) {
|
|
3482
3539
|
context.report({
|
|
3483
3540
|
data: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-react-x",
|
|
3
|
-
"version": "5.18.
|
|
3
|
+
"version": "5.18.2",
|
|
4
4
|
"description": "A set of composable ESLint rules for libraries and frameworks that use React as a UI runtime.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -45,21 +45,21 @@
|
|
|
45
45
|
"string-ts": "^2.3.1",
|
|
46
46
|
"ts-api-utils": "^2.5.0",
|
|
47
47
|
"ts-pattern": "^5.9.0",
|
|
48
|
-
"@eslint-react/ast": "5.18.
|
|
49
|
-
"@eslint-react/
|
|
50
|
-
"@eslint-react/
|
|
51
|
-
"@eslint-react/jsx": "5.18.
|
|
52
|
-
"@eslint-react/
|
|
53
|
-
"@eslint-react/
|
|
48
|
+
"@eslint-react/ast": "5.18.2",
|
|
49
|
+
"@eslint-react/core": "5.18.2",
|
|
50
|
+
"@eslint-react/eslint": "5.18.2",
|
|
51
|
+
"@eslint-react/jsx": "5.18.2",
|
|
52
|
+
"@eslint-react/var": "5.18.2",
|
|
53
|
+
"@eslint-react/shared": "5.18.2"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@types/react": "^19.2.
|
|
57
|
-
"@types/react-dom": "^19.2.
|
|
56
|
+
"@types/react": "^19.2.18",
|
|
57
|
+
"@types/react-dom": "^19.2.4",
|
|
58
58
|
"dedent": "^1.7.2",
|
|
59
|
-
"eslint": "^10.
|
|
59
|
+
"eslint": "^10.8.0",
|
|
60
60
|
"react": "^19.2.8",
|
|
61
61
|
"react-dom": "^19.2.8",
|
|
62
|
-
"tsdown": "^0.22.
|
|
62
|
+
"tsdown": "^0.22.14",
|
|
63
63
|
"tsl": "^1.0.30",
|
|
64
64
|
"tsl-dx": "^0.13.3",
|
|
65
65
|
"typescript": "6.0.3",
|