eslint-plugin-react-x 5.26.0 → 5.26.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 +202 -132
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -135,7 +135,7 @@ const rules$6 = {
|
|
|
135
135
|
//#endregion
|
|
136
136
|
//#region package.json
|
|
137
137
|
var name$6 = "eslint-plugin-react-x";
|
|
138
|
-
var version = "5.26.
|
|
138
|
+
var version = "5.26.2";
|
|
139
139
|
//#endregion
|
|
140
140
|
//#region src/utils/create-rule.ts
|
|
141
141
|
function getDocsUrl(ruleName) {
|
|
@@ -147,8 +147,8 @@ var error_boundaries_default = createRule({
|
|
|
147
147
|
type: "problem",
|
|
148
148
|
docs: { description: "Validates usage of Error Boundaries instead of try/catch for errors in child components." },
|
|
149
149
|
messages: {
|
|
150
|
-
"try-catch-with-
|
|
151
|
-
"try-catch-with-
|
|
150
|
+
"try-catch-with-use": "Use an Error Boundary instead of try/catch around the 'use' hook. The 'use' hook suspends the component, and its errors can only be caught by Error Boundaries.",
|
|
151
|
+
"try-catch-with-jsx": "Use an Error Boundary to catch errors in child components. Try/catch can't catch errors during React's rendering process."
|
|
152
152
|
},
|
|
153
153
|
schema: []
|
|
154
154
|
},
|
|
@@ -1081,15 +1081,6 @@ const MUTATING_ARRAY_METHODS = /* @__PURE__ */ new Set([
|
|
|
1081
1081
|
"splice",
|
|
1082
1082
|
"unshift"
|
|
1083
1083
|
]);
|
|
1084
|
-
function resolveToFunction(context, node, seen = /* @__PURE__ */ new Set()) {
|
|
1085
|
-
const expression = Extract.unwrap(node);
|
|
1086
|
-
if (Check.isFunction(expression)) return expression;
|
|
1087
|
-
if (!Check.isIdentifier(expression) || seen.has(expression)) return null;
|
|
1088
|
-
seen.add(expression);
|
|
1089
|
-
const resolved = resolveOrigin(context, expression);
|
|
1090
|
-
if (resolved == null) return null;
|
|
1091
|
-
return resolveToFunction(context, resolved, seen);
|
|
1092
|
-
}
|
|
1093
1084
|
//#endregion
|
|
1094
1085
|
//#region src/rules/globals/collect.ts
|
|
1095
1086
|
function createFactCollector$6() {
|
|
@@ -1179,6 +1170,15 @@ function resolveGlobalOrigin(context, node, seen = /* @__PURE__ */ new Set()) {
|
|
|
1179
1170
|
if (initializer.type !== AST_NODE_TYPES.Identifier && initializer.type !== AST_NODE_TYPES.MemberExpression) return null;
|
|
1180
1171
|
return resolveGlobalOrigin(context, initializer, seen);
|
|
1181
1172
|
}
|
|
1173
|
+
function resolveToFunction(context, node, seen = /* @__PURE__ */ new Set()) {
|
|
1174
|
+
const expression = Extract.unwrap(node);
|
|
1175
|
+
if (Check.isFunction(expression)) return expression;
|
|
1176
|
+
if (!Check.isIdentifier(expression) || seen.has(expression)) return null;
|
|
1177
|
+
seen.add(expression);
|
|
1178
|
+
const resolved = resolveOrigin(context, expression);
|
|
1179
|
+
if (resolved == null) return null;
|
|
1180
|
+
return resolveToFunction(context, resolved, seen);
|
|
1181
|
+
}
|
|
1182
1182
|
//#endregion
|
|
1183
1183
|
//#region src/rules/globals/effects.ts
|
|
1184
1184
|
function inferGlobalMutations(context, facts) {
|
|
@@ -1313,39 +1313,6 @@ function isNodeWithin(node, ancestor) {
|
|
|
1313
1313
|
}
|
|
1314
1314
|
return false;
|
|
1315
1315
|
}
|
|
1316
|
-
function isComponentPropsDefinition(def, components) {
|
|
1317
|
-
if (def.type !== DefinitionType.Parameter) return false;
|
|
1318
|
-
const fn = def.node;
|
|
1319
|
-
if (!Check.isFunction(fn)) return false;
|
|
1320
|
-
const firstParam = fn.params.at(0);
|
|
1321
|
-
if (firstParam == null || !isNodeWithin(def.name, firstParam)) return false;
|
|
1322
|
-
return components.includes(fn);
|
|
1323
|
-
}
|
|
1324
|
-
function getStateHookName(context, init) {
|
|
1325
|
-
const { additionalStateHooks } = getSettingsFromContext(context);
|
|
1326
|
-
if (core.isUseStateLikeCall(init, additionalStateHooks)) return Extract.getCalleeName(init) ?? "useState";
|
|
1327
|
-
if (core.isUseReducerCall(context, init)) return "useReducer";
|
|
1328
|
-
return null;
|
|
1329
|
-
}
|
|
1330
|
-
function resolveToFunctionNode(context, node, seen = /* @__PURE__ */ new Set()) {
|
|
1331
|
-
const expr = Extract.unwrap(node);
|
|
1332
|
-
if (Check.isFunction(expr)) return expr;
|
|
1333
|
-
if (!Check.isIdentifier(expr) || seen.has(expr)) return null;
|
|
1334
|
-
seen.add(expr);
|
|
1335
|
-
const resolved = resolveOrigin(context, expr);
|
|
1336
|
-
return resolved == null ? null : resolveToFunctionNode(context, resolved, seen);
|
|
1337
|
-
}
|
|
1338
|
-
function resolveVariableOrigin(context, variable, mode = "alias", seen = /* @__PURE__ */ new Set()) {
|
|
1339
|
-
if (seen.has(variable)) return variable;
|
|
1340
|
-
seen.add(variable);
|
|
1341
|
-
const def = variable.defs.length === 1 ? variable.defs[0] : null;
|
|
1342
|
-
if (def?.type !== DefinitionType.Variable || def.node.init == null) return variable;
|
|
1343
|
-
if (mode === "binding" && def.node.id.type === AST_NODE_TYPES.Identifier) return variable;
|
|
1344
|
-
const init = Extract.unwrap(def.node.init);
|
|
1345
|
-
if (!Check.isIdentifier(init)) return variable;
|
|
1346
|
-
const source = findVariable(context.sourceCode.getScope(init), init);
|
|
1347
|
-
return source == null ? variable : resolveVariableOrigin(context, source, mode, seen);
|
|
1348
|
-
}
|
|
1349
1316
|
function isRefLikeName$1(name) {
|
|
1350
1317
|
return name === "ref" || name.endsWith("Ref");
|
|
1351
1318
|
}
|
|
@@ -1354,30 +1321,6 @@ function hasRefLikeNameInChain(node) {
|
|
|
1354
1321
|
if (!Check.isExpression(expr)) return false;
|
|
1355
1322
|
return Extract.getMemberChain(expr).some((member) => Check.isIdentifier(member) && isRefLikeName$1(member.name));
|
|
1356
1323
|
}
|
|
1357
|
-
function isInitializedFromCall(context, node, isCall) {
|
|
1358
|
-
const root = Extract.getMemberChain(node).at(0);
|
|
1359
|
-
if (root == null || !Check.isIdentifier(root)) return false;
|
|
1360
|
-
const variable = findVariable(context.sourceCode.getScope(root), root);
|
|
1361
|
-
if (variable == null) return false;
|
|
1362
|
-
const origin = resolveVariableOrigin(context, variable);
|
|
1363
|
-
const def = origin.defs.length === 1 ? origin.defs[0] : null;
|
|
1364
|
-
if (def?.type !== DefinitionType.Variable || def.node.init == null) return false;
|
|
1365
|
-
const init = Extract.unwrap(def.node.init);
|
|
1366
|
-
return init.type === AST_NODE_TYPES.CallExpression && isCall(init);
|
|
1367
|
-
}
|
|
1368
|
-
function isInitializedFromUseRef(context, node) {
|
|
1369
|
-
const { additionalRefHooks } = getSettingsFromContext(context);
|
|
1370
|
-
return isInitializedFromCall(context, node, (init) => core.isUseRefLikeCall(init, additionalRefHooks));
|
|
1371
|
-
}
|
|
1372
|
-
function isKnownNonMutatingMethodCall(context, node) {
|
|
1373
|
-
const callee = Extract.unwrap(node.callee);
|
|
1374
|
-
return Check.isExpression(callee) && isInitializedFromCall(context, callee, (init) => {
|
|
1375
|
-
return [...KNOWN_MUTATING_HOOKS].some((hook) => core.isAPICall(hook)(context, init));
|
|
1376
|
-
});
|
|
1377
|
-
}
|
|
1378
|
-
function isRefLikeChain(context, node) {
|
|
1379
|
-
return hasRefLikeNameInChain(node) || isInitializedFromUseRef(context, node);
|
|
1380
|
-
}
|
|
1381
1324
|
//#endregion
|
|
1382
1325
|
//#region src/rules/immutability/collect.ts
|
|
1383
1326
|
function createFactCollector$5() {
|
|
@@ -1468,6 +1411,57 @@ function createFactCollector$5() {
|
|
|
1468
1411
|
}
|
|
1469
1412
|
//#endregion
|
|
1470
1413
|
//#region src/rules/immutability/origins.ts
|
|
1414
|
+
function resolveVariableOrigin(context, variable, mode = "alias", seen = /* @__PURE__ */ new Set()) {
|
|
1415
|
+
if (seen.has(variable)) return variable;
|
|
1416
|
+
seen.add(variable);
|
|
1417
|
+
const def = variable.defs.length === 1 ? variable.defs[0] : null;
|
|
1418
|
+
if (def?.type !== DefinitionType.Variable || def.node.init == null) return variable;
|
|
1419
|
+
if (mode === "binding" && def.node.id.type === AST_NODE_TYPES.Identifier) return variable;
|
|
1420
|
+
const init = Extract.unwrap(def.node.init);
|
|
1421
|
+
if (!Check.isIdentifier(init)) return variable;
|
|
1422
|
+
const source = findVariable(context.sourceCode.getScope(init), init);
|
|
1423
|
+
return source == null ? variable : resolveVariableOrigin(context, source, mode, seen);
|
|
1424
|
+
}
|
|
1425
|
+
function resolveToFunctionNode(context, node, seen = /* @__PURE__ */ new Set()) {
|
|
1426
|
+
const expr = Extract.unwrap(node);
|
|
1427
|
+
if (Check.isFunction(expr)) return expr;
|
|
1428
|
+
if (!Check.isIdentifier(expr) || seen.has(expr)) return null;
|
|
1429
|
+
seen.add(expr);
|
|
1430
|
+
const resolved = resolveOrigin(context, expr);
|
|
1431
|
+
return resolved == null ? null : resolveToFunctionNode(context, resolved, seen);
|
|
1432
|
+
}
|
|
1433
|
+
function isComponentPropsDefinition(def, components) {
|
|
1434
|
+
if (def.type !== DefinitionType.Parameter) return false;
|
|
1435
|
+
const fn = def.node;
|
|
1436
|
+
if (!Check.isFunction(fn)) return false;
|
|
1437
|
+
const firstParam = fn.params.at(0);
|
|
1438
|
+
if (firstParam == null || !isNodeWithin(def.name, firstParam)) return false;
|
|
1439
|
+
return components.includes(fn);
|
|
1440
|
+
}
|
|
1441
|
+
function getStateHookName(context, init) {
|
|
1442
|
+
const { additionalStateHooks } = getSettingsFromContext(context);
|
|
1443
|
+
if (core.isUseStateLikeCall(init, additionalStateHooks)) return Extract.getCalleeName(init) ?? "useState";
|
|
1444
|
+
if (core.isUseReducerCall(context, init)) return "useReducer";
|
|
1445
|
+
return null;
|
|
1446
|
+
}
|
|
1447
|
+
function isInitializedFromCall(context, node, isCall) {
|
|
1448
|
+
const root = Extract.getMemberChain(node).at(0);
|
|
1449
|
+
if (root == null || !Check.isIdentifier(root)) return false;
|
|
1450
|
+
const variable = findVariable(context.sourceCode.getScope(root), root);
|
|
1451
|
+
if (variable == null) return false;
|
|
1452
|
+
const origin = resolveVariableOrigin(context, variable);
|
|
1453
|
+
const def = origin.defs.length === 1 ? origin.defs[0] : null;
|
|
1454
|
+
if (def?.type !== DefinitionType.Variable || def.node.init == null) return false;
|
|
1455
|
+
const init = Extract.unwrap(def.node.init);
|
|
1456
|
+
return init.type === AST_NODE_TYPES.CallExpression && isCall(init);
|
|
1457
|
+
}
|
|
1458
|
+
function isInitializedFromUseRef(context, node) {
|
|
1459
|
+
const { additionalRefHooks } = getSettingsFromContext(context);
|
|
1460
|
+
return isInitializedFromCall(context, node, (init) => core.isUseRefLikeCall(init, additionalRefHooks));
|
|
1461
|
+
}
|
|
1462
|
+
function isRefLikeChain(context, node) {
|
|
1463
|
+
return hasRefLikeNameInChain(node) || isInitializedFromUseRef(context, node);
|
|
1464
|
+
}
|
|
1471
1465
|
function classifyFrozenOrigin(context, variable, components, mode = "alias", seen = /* @__PURE__ */ new Set()) {
|
|
1472
1466
|
if (seen.has(variable)) return null;
|
|
1473
1467
|
seen.add(variable);
|
|
@@ -1537,6 +1531,12 @@ function isRefMutation(context, mutation) {
|
|
|
1537
1531
|
if (Check.isIdentifier(mutation.target)) return isRefLikeName$1(mutation.target.name);
|
|
1538
1532
|
return isRefLikeChain(context, mutation.target);
|
|
1539
1533
|
}
|
|
1534
|
+
function isKnownNonMutatingMethodCall(context, node) {
|
|
1535
|
+
const callee = Extract.unwrap(node.callee);
|
|
1536
|
+
return Check.isExpression(callee) && isInitializedFromCall(context, callee, (init) => {
|
|
1537
|
+
return [...KNOWN_MUTATING_HOOKS].some((hook) => core.isAPICall(hook)(context, init));
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
1540
|
function inferMutableFunctions(context, mutations) {
|
|
1541
1541
|
const mutableFunctions = /* @__PURE__ */ new Map();
|
|
1542
1542
|
for (const mutation of mutations) {
|
|
@@ -1627,9 +1627,9 @@ var immutability_default = createRule({
|
|
|
1627
1627
|
type: "problem",
|
|
1628
1628
|
docs: { description: "Validates against mutating props, state, and other immutable values, including through functions passed into frozen contexts such as JSX props, hook arguments, and hook return values." },
|
|
1629
1629
|
messages: {
|
|
1630
|
-
"direct-mutation": "Do not mutate '{{name}}' directly. {{detail}}",
|
|
1631
1630
|
"indirect-mutation": "This function may (indirectly) reassign or modify '{{name}}' after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.",
|
|
1632
|
-
"mutation-site": "This modifies '{{name}}'."
|
|
1631
|
+
"mutation-site": "This modifies '{{name}}'.",
|
|
1632
|
+
"direct-mutation": "Do not mutate '{{name}}' directly. {{detail}}"
|
|
1633
1633
|
},
|
|
1634
1634
|
schema: []
|
|
1635
1635
|
},
|
|
@@ -2177,7 +2177,7 @@ var no_context_provider_default = createRule({
|
|
|
2177
2177
|
hasSuggestions: true,
|
|
2178
2178
|
messages: {
|
|
2179
2179
|
default: "In React 19, you can render '<Context>' as a provider instead of '<Context.Provider>'.",
|
|
2180
|
-
|
|
2180
|
+
suggest: "Replace '<Context.Provider>' with '<Context>'."
|
|
2181
2181
|
},
|
|
2182
2182
|
schema: []
|
|
2183
2183
|
},
|
|
@@ -2206,7 +2206,7 @@ function create$35(context) {
|
|
|
2206
2206
|
if (closingElement == null) return fixer.replaceText(openingElement.name, contextFullName);
|
|
2207
2207
|
return [fixer.replaceText(openingElement.name, contextFullName), fixer.replaceText(closingElement.name, contextFullName)];
|
|
2208
2208
|
},
|
|
2209
|
-
messageId: "
|
|
2209
|
+
messageId: "suggest"
|
|
2210
2210
|
}]
|
|
2211
2211
|
});
|
|
2212
2212
|
} };
|
|
@@ -2354,7 +2354,7 @@ var no_forward_ref_default = createRule({
|
|
|
2354
2354
|
hasSuggestions: true,
|
|
2355
2355
|
messages: {
|
|
2356
2356
|
default: "In React 19, 'forwardRef' is no longer necessary. Pass 'ref' as a prop instead.",
|
|
2357
|
-
|
|
2357
|
+
suggest: "Replace 'forwardRef' with passing 'ref' as a prop."
|
|
2358
2358
|
},
|
|
2359
2359
|
schema: []
|
|
2360
2360
|
},
|
|
@@ -2370,7 +2370,7 @@ function create$31(context) {
|
|
|
2370
2370
|
const id = core.getFunctionId(node);
|
|
2371
2371
|
const suggest = couldFix(context, node) ? [{
|
|
2372
2372
|
fix: buildFix(context, node),
|
|
2373
|
-
messageId: "
|
|
2373
|
+
messageId: "suggest"
|
|
2374
2374
|
}] : [];
|
|
2375
2375
|
context.report({
|
|
2376
2376
|
messageId: "default",
|
|
@@ -3315,6 +3315,11 @@ function create$12(context, [options]) {
|
|
|
3315
3315
|
const { safeDefaultProps = [] } = options;
|
|
3316
3316
|
const safePatterns = safeDefaultProps.map((s) => toRegExp(s));
|
|
3317
3317
|
return merge(visitor, {
|
|
3318
|
+
[SEL_OBJECT_DESTRUCTURING_VARIABLE_DECLARATOR](node) {
|
|
3319
|
+
const enclosingFunction = Traverse.findParent(node, Check.isFunction);
|
|
3320
|
+
if (enclosingFunction == null) return;
|
|
3321
|
+
getOrInsertComputed(declarators, enclosingFunction, () => []).push(node);
|
|
3322
|
+
},
|
|
3318
3323
|
"Program:exit"(program) {
|
|
3319
3324
|
for (const { node: component } of api.getAllComponents(program)) {
|
|
3320
3325
|
const { params } = component;
|
|
@@ -3344,11 +3349,6 @@ function create$12(context, [options]) {
|
|
|
3344
3349
|
});
|
|
3345
3350
|
}
|
|
3346
3351
|
}
|
|
3347
|
-
},
|
|
3348
|
-
[SEL_OBJECT_DESTRUCTURING_VARIABLE_DECLARATOR](node) {
|
|
3349
|
-
const enclosingFunction = Traverse.findParent(node, Check.isFunction);
|
|
3350
|
-
if (enclosingFunction == null) return;
|
|
3351
|
-
getOrInsertComputed(declarators, enclosingFunction, () => []).push(node);
|
|
3352
3352
|
}
|
|
3353
3353
|
});
|
|
3354
3354
|
}
|
|
@@ -3371,7 +3371,7 @@ const LIFECYCLE_METHODS = /* @__PURE__ */ new Set([
|
|
|
3371
3371
|
"UNSAFE_componentWillReceiveProps",
|
|
3372
3372
|
"UNSAFE_componentWillUpdate"
|
|
3373
3373
|
]);
|
|
3374
|
-
const
|
|
3374
|
+
const KNOWN_HOST_CONVENTION_METHODS = /* @__PURE__ */ new Set([
|
|
3375
3375
|
"blur",
|
|
3376
3376
|
"focus",
|
|
3377
3377
|
"measure",
|
|
@@ -3379,6 +3379,22 @@ const HOST_CONVENTION_METHODS = /* @__PURE__ */ new Set([
|
|
|
3379
3379
|
"measureLayout",
|
|
3380
3380
|
"setNativeProps"
|
|
3381
3381
|
]);
|
|
3382
|
+
function isExportedClass(node) {
|
|
3383
|
+
switch (node.parent.type) {
|
|
3384
|
+
case AST_NODE_TYPES.ExportDefaultDeclaration:
|
|
3385
|
+
case AST_NODE_TYPES.ExportNamedDeclaration: return true;
|
|
3386
|
+
case AST_NODE_TYPES.VariableDeclarator: return node.parent.parent.parent.type === AST_NODE_TYPES.ExportNamedDeclaration;
|
|
3387
|
+
default: return false;
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
function isPublicMethod(def) {
|
|
3391
|
+
const member = def.parent;
|
|
3392
|
+
switch (member.type) {
|
|
3393
|
+
case AST_NODE_TYPES.MethodDefinition: return member.accessibility == null || member.accessibility === "public";
|
|
3394
|
+
case AST_NODE_TYPES.PropertyDefinition: return (member.accessibility == null || member.accessibility === "public") && (member.value?.type === AST_NODE_TYPES.ArrowFunctionExpression || member.value?.type === AST_NODE_TYPES.FunctionExpression);
|
|
3395
|
+
default: return false;
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3382
3398
|
var no_unused_class_component_members_default = createRule({
|
|
3383
3399
|
meta: {
|
|
3384
3400
|
type: "suggestion",
|
|
@@ -3398,13 +3414,15 @@ function create$11(context) {
|
|
|
3398
3414
|
const defs = propertyDefs.get(node);
|
|
3399
3415
|
if (defs == null) return;
|
|
3400
3416
|
const usages = propertyUsages.get(node);
|
|
3417
|
+
const exported = isExportedClass(node);
|
|
3401
3418
|
for (const def of defs) {
|
|
3402
3419
|
const methodName = def.name;
|
|
3403
3420
|
if (usages?.has(methodName) ?? false) continue;
|
|
3404
3421
|
if (LIFECYCLE_METHODS.has(methodName)) {
|
|
3405
3422
|
if (methodName === "shouldComponentUpdate" && core.isPureComponent(node)) {} else continue;
|
|
3406
3423
|
}
|
|
3407
|
-
if (
|
|
3424
|
+
if (KNOWN_HOST_CONVENTION_METHODS.has(methodName)) continue;
|
|
3425
|
+
if (exported && isPublicMethod(def)) continue;
|
|
3408
3426
|
context.report({
|
|
3409
3427
|
data: {
|
|
3410
3428
|
className: id != null ? context.sourceCode.getText(id) : "Component",
|
|
@@ -3455,6 +3473,7 @@ function collectUsedPropKeysOfParameter(context, usedPropKeys, parameter) {
|
|
|
3455
3473
|
switch (parameter.type) {
|
|
3456
3474
|
case AST_NODE_TYPES.Identifier: return collectUsedPropKeysOfIdentifier(context, usedPropKeys, parameter);
|
|
3457
3475
|
case AST_NODE_TYPES.ObjectPattern: return collectUsedPropKeysOfObjectPattern(context, usedPropKeys, parameter);
|
|
3476
|
+
case AST_NODE_TYPES.AssignmentPattern: return collectUsedPropKeysOfParameter(context, usedPropKeys, parameter.left);
|
|
3458
3477
|
default: return false;
|
|
3459
3478
|
}
|
|
3460
3479
|
}
|
|
@@ -3498,14 +3517,20 @@ function collectUsedPropKeysOfReference(context, usedPropKeys, ref) {
|
|
|
3498
3517
|
return true;
|
|
3499
3518
|
}
|
|
3500
3519
|
break;
|
|
3501
|
-
case AST_NODE_TYPES.VariableDeclarator:
|
|
3520
|
+
case AST_NODE_TYPES.VariableDeclarator:
|
|
3521
|
+
if (parent.init !== valueNode) break;
|
|
3522
|
+
if (parent.id.type === AST_NODE_TYPES.ObjectPattern) return collectUsedPropKeysOfObjectPattern(context, usedPropKeys, parent.id);
|
|
3523
|
+
if (parent.id.type === AST_NODE_TYPES.Identifier) return collectUsedPropKeysOfIdentifier(context, usedPropKeys, parent.id);
|
|
3502
3524
|
}
|
|
3503
3525
|
return false;
|
|
3504
3526
|
}
|
|
3505
3527
|
function getKeyOfExpression(expr, computed = false) {
|
|
3506
3528
|
switch (expr.type) {
|
|
3507
3529
|
case AST_NODE_TYPES.Identifier: return computed ? null : expr.name;
|
|
3508
|
-
case AST_NODE_TYPES.Literal:
|
|
3530
|
+
case AST_NODE_TYPES.Literal:
|
|
3531
|
+
if (isString(expr.value)) return expr.value;
|
|
3532
|
+
break;
|
|
3533
|
+
case AST_NODE_TYPES.TemplateLiteral: if (expr.expressions.length === 0 && expr.quasis.length === 1) return expr.quasis[0]?.value.cooked ?? null;
|
|
3509
3534
|
}
|
|
3510
3535
|
return null;
|
|
3511
3536
|
}
|
|
@@ -3529,15 +3554,15 @@ function create$10(context) {
|
|
|
3529
3554
|
for (const component of api.getAllComponents(program)) {
|
|
3530
3555
|
const [props] = component.node.params;
|
|
3531
3556
|
if (props == null) continue;
|
|
3532
|
-
const usedPropKeys = /* @__PURE__ */ new Set();
|
|
3533
|
-
if (!collectUsedPropKeysOfParameter(context, usedPropKeys, props)) continue;
|
|
3534
3557
|
const tsNode = services.esTreeNodeToTSNodeMap.get(props);
|
|
3535
3558
|
const declaredProps = checker.getTypeAtLocation(tsNode).getProperties();
|
|
3559
|
+
const usedPropKeys = /* @__PURE__ */ new Set();
|
|
3560
|
+
const couldFindAllUsedPropKeys = collectUsedPropKeysOfParameter(context, usedPropKeys, props);
|
|
3536
3561
|
for (const declaredProp of declaredProps) {
|
|
3537
3562
|
const declaration = declaredProp.getDeclarations()?.[0];
|
|
3538
3563
|
if (declaration == null) continue;
|
|
3539
3564
|
if (!totalDeclaredProps.has(declaration)) totalDeclaredProps.set(declaration, declaredProp);
|
|
3540
|
-
if (usedPropKeys.has(declaredProp.name)) totalUsedDeclarations.add(declaration);
|
|
3565
|
+
if (!couldFindAllUsedPropKeys || usedPropKeys.has(declaredProp.name)) totalUsedDeclarations.add(declaration);
|
|
3541
3566
|
}
|
|
3542
3567
|
}
|
|
3543
3568
|
for (const [declaration, symbol] of totalDeclaredProps.entries()) if (!totalUsedDeclarations.has(declaration)) reportUnusedProp(context, services, symbol);
|
|
@@ -3609,7 +3634,7 @@ var no_use_context_default = createRule({
|
|
|
3609
3634
|
hasSuggestions: true,
|
|
3610
3635
|
messages: {
|
|
3611
3636
|
default: "In React 19, 'use' is preferred over 'useContext' because it is more flexible.",
|
|
3612
|
-
|
|
3637
|
+
suggest: "Replace 'useContext' with 'use'."
|
|
3613
3638
|
},
|
|
3614
3639
|
schema: []
|
|
3615
3640
|
},
|
|
@@ -3633,7 +3658,7 @@ function create$8(context) {
|
|
|
3633
3658
|
}
|
|
3634
3659
|
return null;
|
|
3635
3660
|
},
|
|
3636
|
-
messageId: "
|
|
3661
|
+
messageId: "suggest"
|
|
3637
3662
|
}]
|
|
3638
3663
|
});
|
|
3639
3664
|
} };
|
|
@@ -6541,12 +6566,14 @@ function createSetStateResolver$1(context) {
|
|
|
6541
6566
|
isUseStateCall
|
|
6542
6567
|
};
|
|
6543
6568
|
}
|
|
6544
|
-
function isInitializedFromRef(context,
|
|
6545
|
-
const
|
|
6569
|
+
function isInitializedFromRef(context, id, refParams, seen = /* @__PURE__ */ new Set()) {
|
|
6570
|
+
const name = id.name;
|
|
6546
6571
|
if (seen.has(name)) return false;
|
|
6547
6572
|
seen.add(name);
|
|
6548
|
-
|
|
6573
|
+
const variable = findVariable(context.sourceCode.getScope(id), name);
|
|
6574
|
+
for (const def of variable?.defs ?? []) {
|
|
6549
6575
|
if (def.type === DefinitionType.Parameter) {
|
|
6576
|
+
if (refParams?.has(def.name) === true) return true;
|
|
6550
6577
|
if (def.name.type === AST_NODE_TYPES.Identifier && (def.name.name === "ref" || def.name.name.endsWith("Ref"))) return true;
|
|
6551
6578
|
continue;
|
|
6552
6579
|
}
|
|
@@ -6554,32 +6581,58 @@ function isInitializedFromRef(context, name, initialScope, seen = /* @__PURE__ *
|
|
|
6554
6581
|
if (node.type !== AST_NODE_TYPES.VariableDeclarator) continue;
|
|
6555
6582
|
const init = node.init == null ? null : Extract.unwrap(node.init);
|
|
6556
6583
|
if (init == null) continue;
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6584
|
+
if (isExpressionFromRef(context, init, refParams, seen)) return true;
|
|
6585
|
+
}
|
|
6586
|
+
for (const reference of variable?.references ?? []) {
|
|
6587
|
+
if (!reference.isWrite()) continue;
|
|
6588
|
+
const writeExpr = reference.writeExpr == null ? null : Extract.unwrap(reference.writeExpr);
|
|
6589
|
+
if (writeExpr == null) continue;
|
|
6590
|
+
if (writeExpr.range[1] > id.range[0]) continue;
|
|
6591
|
+
const writeFn = Traverse.findParent(writeExpr, Check.isFunction);
|
|
6592
|
+
if (writeFn != null && !isSelfOrAncestorFunction(writeFn, id)) continue;
|
|
6593
|
+
if (isExpressionFromRef(context, writeExpr, refParams, seen)) return true;
|
|
6594
|
+
}
|
|
6595
|
+
return false;
|
|
6596
|
+
}
|
|
6597
|
+
function isSelfOrAncestorFunction(fn, node) {
|
|
6598
|
+
let current = Traverse.findParent(node, Check.isFunction);
|
|
6599
|
+
while (current != null) {
|
|
6600
|
+
if (current === fn) return true;
|
|
6601
|
+
current = Traverse.findParent(current, Check.isFunction);
|
|
6602
|
+
}
|
|
6603
|
+
return false;
|
|
6604
|
+
}
|
|
6605
|
+
function isExpressionFromRef(context, init, refParams, seen) {
|
|
6606
|
+
const { additionalRefHooks } = getSettingsFromContext(context);
|
|
6607
|
+
switch (true) {
|
|
6608
|
+
case init.type === AST_NODE_TYPES.MemberExpression: {
|
|
6609
|
+
let current = init;
|
|
6610
|
+
while (current.type === AST_NODE_TYPES.MemberExpression) {
|
|
6611
|
+
const property = Extract.unwrap(current.property);
|
|
6612
|
+
const object = Extract.unwrap(current.object);
|
|
6613
|
+
if (!current.computed && Check.isIdentifier(property, "current")) {
|
|
6614
|
+
const sourceName = getRefSourceName(object);
|
|
6615
|
+
if (sourceName === "ref" || sourceName?.endsWith("Ref") === true) return true;
|
|
6616
|
+
if (object.type === AST_NODE_TYPES.Identifier && isParameterId(context, object)) return true;
|
|
6568
6617
|
}
|
|
6569
|
-
|
|
6570
|
-
return getNestedIdentifiers(init).some((id) => isInitializedFromRef(context, id.name, context.sourceCode.getScope(id), seen));
|
|
6618
|
+
current = object;
|
|
6571
6619
|
}
|
|
6572
|
-
|
|
6573
|
-
|
|
6620
|
+
if (Check.isIdentifier(current) && (current.name === "ref" || current.name.endsWith("Ref"))) return true;
|
|
6621
|
+
return getNestedIdentifiers(init).some((id) => isInitializedFromRef(context, id, refParams, seen));
|
|
6574
6622
|
}
|
|
6623
|
+
case init.type === AST_NODE_TYPES.CallExpression && core.isUseRefLikeCall(init, additionalRefHooks): return true;
|
|
6624
|
+
default: return getNestedIdentifiers(init).some((id) => isInitializedFromRef(context, id, refParams, seen));
|
|
6575
6625
|
}
|
|
6576
|
-
return false;
|
|
6577
6626
|
}
|
|
6578
|
-
function isArgumentUsingRefValue(context, node) {
|
|
6627
|
+
function isArgumentUsingRefValue(context, node, refParams) {
|
|
6579
6628
|
const isUsingRefValue = (n) => {
|
|
6580
6629
|
switch (n.type) {
|
|
6581
|
-
case AST_NODE_TYPES.Identifier: return isInitializedFromRef(context, n
|
|
6582
|
-
case AST_NODE_TYPES.MemberExpression:
|
|
6630
|
+
case AST_NODE_TYPES.Identifier: return isInitializedFromRef(context, n, refParams);
|
|
6631
|
+
case AST_NODE_TYPES.MemberExpression: {
|
|
6632
|
+
const property = Extract.unwrap(n.property);
|
|
6633
|
+
if (!n.computed && Check.isIdentifier(property, "current") && n.object.type === AST_NODE_TYPES.Identifier && isParameterId(context, n.object)) return true;
|
|
6634
|
+
return isUsingRefValue(n.object);
|
|
6635
|
+
}
|
|
6583
6636
|
case AST_NODE_TYPES.CallExpression: return isUsingRefValue(n.callee) || getNestedIdentifiers(n).some(isUsingRefValue);
|
|
6584
6637
|
case AST_NODE_TYPES.ChainExpression: return isUsingRefValue(n.expression);
|
|
6585
6638
|
case AST_NODE_TYPES.BinaryExpression:
|
|
@@ -6601,22 +6654,22 @@ function isArgumentUsingRefValue(context, node) {
|
|
|
6601
6654
|
if (isUsingRefValue(node)) return true;
|
|
6602
6655
|
return Check.isFunction(node) && context.sourceCode.getScope(node.body).references.some((r) => isUsingRefValue(r.identifier));
|
|
6603
6656
|
}
|
|
6604
|
-
function isRefGatedContext(context, node) {
|
|
6657
|
+
function isRefGatedContext(context, node, refParams) {
|
|
6605
6658
|
let child = node;
|
|
6606
6659
|
let current = node.parent;
|
|
6607
6660
|
while (current != null) {
|
|
6608
6661
|
if (Check.isFunction(current)) break;
|
|
6609
6662
|
if (current.type === AST_NODE_TYPES.IfStatement) {
|
|
6610
|
-
if (isRefInExpression(context, current.test)) return true;
|
|
6663
|
+
if (isRefInExpression(context, current.test, refParams)) return true;
|
|
6611
6664
|
}
|
|
6612
6665
|
if (current.type === AST_NODE_TYPES.ConditionalExpression) {
|
|
6613
|
-
if (isRefInExpression(context, current.test)) return true;
|
|
6666
|
+
if (isRefInExpression(context, current.test, refParams)) return true;
|
|
6614
6667
|
}
|
|
6615
6668
|
if (current.type === AST_NODE_TYPES.BlockStatement) {
|
|
6616
6669
|
const index = current.body.findIndex((statement) => statement === child);
|
|
6617
6670
|
for (const statement of current.body.slice(0, Math.max(0, index))) {
|
|
6618
6671
|
if (statement.type !== AST_NODE_TYPES.IfStatement) continue;
|
|
6619
|
-
if (!isRefInExpression(context, statement.test)) continue;
|
|
6672
|
+
if (!isRefInExpression(context, statement.test, refParams)) continue;
|
|
6620
6673
|
if (isTerminatingStatement(statement.consequent)) return true;
|
|
6621
6674
|
}
|
|
6622
6675
|
}
|
|
@@ -6625,8 +6678,11 @@ function isRefGatedContext(context, node) {
|
|
|
6625
6678
|
}
|
|
6626
6679
|
return false;
|
|
6627
6680
|
}
|
|
6628
|
-
function isRefInExpression(context, node) {
|
|
6629
|
-
return getNestedIdentifiers(node).some((id) => isInitializedFromRef(context, id
|
|
6681
|
+
function isRefInExpression(context, node, refParams) {
|
|
6682
|
+
return getNestedIdentifiers(node).some((id) => isInitializedFromRef(context, id, refParams));
|
|
6683
|
+
}
|
|
6684
|
+
function isParameterId(context, id) {
|
|
6685
|
+
return findVariable(context.sourceCode.getScope(id), id)?.defs.some((def) => def.type === DefinitionType.Parameter) ?? false;
|
|
6630
6686
|
}
|
|
6631
6687
|
function getRefSourceName(node) {
|
|
6632
6688
|
switch (node.type) {
|
|
@@ -6693,12 +6749,25 @@ function inferViolations$2(context, facts) {
|
|
|
6693
6749
|
}
|
|
6694
6750
|
if (isUseEffectCall(parent)) getOrInsertComputed(setStateInEffectSetup, parent, () => []).push(node);
|
|
6695
6751
|
}
|
|
6696
|
-
const isExemptSetStateCall = (setStateCall) => {
|
|
6752
|
+
const isExemptSetStateCall = (setStateCall, refParams) => {
|
|
6697
6753
|
const callNode = getSetStateCallExpression(setStateCall);
|
|
6698
|
-
if (isRefGatedContext(context, callNode)) return true;
|
|
6754
|
+
if (isRefGatedContext(context, callNode, refParams)) return true;
|
|
6699
6755
|
if (callNode.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
6700
6756
|
const args0 = callNode.arguments.at(0);
|
|
6701
|
-
return args0 != null && isArgumentUsingRefValue(context, args0);
|
|
6757
|
+
return args0 != null && isArgumentUsingRefValue(context, args0, refParams);
|
|
6758
|
+
};
|
|
6759
|
+
const getRefDerivedParams = (call, callee) => {
|
|
6760
|
+
const origin = resolveOrigin(context, callee);
|
|
6761
|
+
if (origin == null || !Check.isFunction(origin)) return void 0;
|
|
6762
|
+
const refParams = /* @__PURE__ */ new Set();
|
|
6763
|
+
origin.params.forEach((param, index) => {
|
|
6764
|
+
const paramId = param.type === AST_NODE_TYPES.Identifier ? param : param.type === AST_NODE_TYPES.AssignmentPattern && param.left.type === AST_NODE_TYPES.Identifier ? param.left : null;
|
|
6765
|
+
if (paramId == null) return;
|
|
6766
|
+
const arg = call.arguments.at(index);
|
|
6767
|
+
if (arg == null || arg.type === AST_NODE_TYPES.SpreadElement) return;
|
|
6768
|
+
if (isArgumentUsingRefValue(context, arg)) refParams.add(paramId);
|
|
6769
|
+
});
|
|
6770
|
+
return refParams.size > 0 ? refParams : void 0;
|
|
6702
6771
|
};
|
|
6703
6772
|
const getSetStateCalls = (id) => {
|
|
6704
6773
|
const variable = findVariable(context.sourceCode.getScope(id), id);
|
|
@@ -6719,11 +6788,12 @@ function inferViolations$2(context, facts) {
|
|
|
6719
6788
|
node: call
|
|
6720
6789
|
});
|
|
6721
6790
|
}
|
|
6722
|
-
for (const
|
|
6723
|
-
const unwrappedCallee = Extract.unwrap(callee);
|
|
6791
|
+
for (const trackedCall of trackedFnCalls) {
|
|
6792
|
+
const unwrappedCallee = Extract.unwrap(trackedCall.callee);
|
|
6724
6793
|
if (!Check.isIdentifier(unwrappedCallee)) continue;
|
|
6794
|
+
const refParams = getRefDerivedParams(trackedCall, unwrappedCallee);
|
|
6725
6795
|
for (const setStateCall of getSetStateCalls(unwrappedCallee)) {
|
|
6726
|
-
if (isExemptSetStateCall(setStateCall)) continue;
|
|
6796
|
+
if (isExemptSetStateCall(setStateCall, refParams)) continue;
|
|
6727
6797
|
violations.push({
|
|
6728
6798
|
name: getCallName(setStateCall, getText),
|
|
6729
6799
|
node: setStateCall
|
|
@@ -7126,6 +7196,14 @@ function create$2(context) {
|
|
|
7126
7196
|
node
|
|
7127
7197
|
});
|
|
7128
7198
|
},
|
|
7199
|
+
WithStatement(node) {
|
|
7200
|
+
const func = Traverse.findParent(node, Check.isFunction);
|
|
7201
|
+
if (func == null) return;
|
|
7202
|
+
withStmts.push({
|
|
7203
|
+
func,
|
|
7204
|
+
node
|
|
7205
|
+
});
|
|
7206
|
+
},
|
|
7129
7207
|
"Program:exit"(node) {
|
|
7130
7208
|
const comps = fc.api.getAllComponents(node);
|
|
7131
7209
|
const hooks = hc.api.getAllHooks(node);
|
|
@@ -7144,14 +7222,6 @@ function create$2(context) {
|
|
|
7144
7222
|
node
|
|
7145
7223
|
});
|
|
7146
7224
|
}
|
|
7147
|
-
},
|
|
7148
|
-
WithStatement(node) {
|
|
7149
|
-
const func = Traverse.findParent(node, Check.isFunction);
|
|
7150
|
-
if (func == null) return;
|
|
7151
|
-
withStmts.push({
|
|
7152
|
-
func,
|
|
7153
|
-
node
|
|
7154
|
-
});
|
|
7155
7225
|
}
|
|
7156
7226
|
});
|
|
7157
7227
|
}
|
|
@@ -7411,8 +7481,8 @@ var use_state_default = createRule({
|
|
|
7411
7481
|
defaultOptions: [...defaultOptions],
|
|
7412
7482
|
docs: { description: "Enforces correct usage of 'useState', including destructuring, symmetric naming of the value and setter, and wrapping expensive initializers in a lazy initializer function." },
|
|
7413
7483
|
messages: {
|
|
7414
|
-
"invalid-assignment": "useState should be destructured into a value and setter pair, e.g., const [state, setState] = useState(...).",
|
|
7415
7484
|
"invalid-initialization": "To prevent re-computation, consider using lazy initial state for useState calls that involve function calls. Ex: 'useState(() => getValue())'.",
|
|
7485
|
+
"invalid-assignment": "useState should be destructured into a value and setter pair, e.g., const [state, setState] = useState(...).",
|
|
7416
7486
|
"invalid-setter-name": "The setter should be named 'set' followed by the capitalized state variable name, e.g., 'setState' for 'state'."
|
|
7417
7487
|
},
|
|
7418
7488
|
schema
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-react-x",
|
|
3
|
-
"version": "5.26.
|
|
3
|
+
"version": "5.26.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",
|
|
@@ -36,12 +36,12 @@
|
|
|
36
36
|
"dist"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@eslint-react/ast": "5.26.
|
|
40
|
-
"@eslint-react/core": "5.26.
|
|
41
|
-
"@eslint-react/eslint": "5.26.
|
|
42
|
-
"@eslint-react/jsx": "5.26.
|
|
43
|
-
"@eslint-react/shared": "5.26.
|
|
44
|
-
"@eslint-react/var": "5.26.
|
|
39
|
+
"@eslint-react/ast": "5.26.2",
|
|
40
|
+
"@eslint-react/core": "5.26.2",
|
|
41
|
+
"@eslint-react/eslint": "5.26.2",
|
|
42
|
+
"@eslint-react/jsx": "5.26.2",
|
|
43
|
+
"@eslint-react/shared": "5.26.2",
|
|
44
|
+
"@eslint-react/var": "5.26.2",
|
|
45
45
|
"@typescript-eslint/scope-manager": "^8.71.1",
|
|
46
46
|
"@typescript-eslint/types": "^8.71.1",
|
|
47
47
|
"@typescript-eslint/typescript-estree": "^8.71.1",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"react-dom": "^19.3.0",
|
|
65
65
|
"tsdown": "^0.23.1",
|
|
66
66
|
"tsl": "^1.0.30",
|
|
67
|
-
"tsl-dx": "^0.14.
|
|
67
|
+
"tsl-dx": "^0.14.1",
|
|
68
68
|
"typescript": "6.0.3"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|