eslint-plugin-react-x 5.11.3 → 5.12.0-beta.1
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 +166 -204
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -144,7 +144,7 @@ const rules$6 = {
|
|
|
144
144
|
//#endregion
|
|
145
145
|
//#region package.json
|
|
146
146
|
var name$6 = "eslint-plugin-react-x";
|
|
147
|
-
var version = "5.
|
|
147
|
+
var version = "5.12.0-beta.1";
|
|
148
148
|
|
|
149
149
|
//#endregion
|
|
150
150
|
//#region src/utils/create-rule.ts
|
|
@@ -1146,7 +1146,7 @@ function getUnknownDependenciesMessage(reactiveHookName) {
|
|
|
1146
1146
|
* Array methods that mutate the array in place.
|
|
1147
1147
|
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
|
|
1148
1148
|
*/
|
|
1149
|
-
const MUTATING_ARRAY_METHODS
|
|
1149
|
+
const MUTATING_ARRAY_METHODS = /* @__PURE__ */ new Set([
|
|
1150
1150
|
"copyWithin",
|
|
1151
1151
|
"fill",
|
|
1152
1152
|
"pop",
|
|
@@ -1253,7 +1253,7 @@ function create$49(context) {
|
|
|
1253
1253
|
if (callee.type !== AST_NODE_TYPES.MemberExpression) return;
|
|
1254
1254
|
const { object, property } = callee;
|
|
1255
1255
|
if (property.type !== AST_NODE_TYPES.Identifier) return;
|
|
1256
|
-
if (!MUTATING_ARRAY_METHODS
|
|
1256
|
+
if (!MUTATING_ARRAY_METHODS.has(property.name)) return;
|
|
1257
1257
|
const rootId = Extract.getRootIdentifier(object);
|
|
1258
1258
|
if (rootId == null) return;
|
|
1259
1259
|
if (!isGlobalOrModuleVariable(rootId)) return;
|
|
@@ -1281,21 +1281,52 @@ function create$49(context) {
|
|
|
1281
1281
|
//#endregion
|
|
1282
1282
|
//#region src/rules/immutability/lib.ts
|
|
1283
1283
|
/**
|
|
1284
|
-
*
|
|
1284
|
+
* Methods that mutate their receiver in place.
|
|
1285
1285
|
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
|
|
1286
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
|
|
1287
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
|
|
1286
1288
|
*/
|
|
1287
|
-
const
|
|
1289
|
+
const MUTATING_METHODS = /* @__PURE__ */ new Set([
|
|
1290
|
+
"add",
|
|
1291
|
+
"clear",
|
|
1288
1292
|
"copyWithin",
|
|
1293
|
+
"delete",
|
|
1289
1294
|
"fill",
|
|
1290
1295
|
"pop",
|
|
1291
1296
|
"push",
|
|
1292
1297
|
"reverse",
|
|
1298
|
+
"set",
|
|
1293
1299
|
"shift",
|
|
1294
1300
|
"sort",
|
|
1295
1301
|
"splice",
|
|
1296
1302
|
"unshift"
|
|
1297
1303
|
]);
|
|
1298
1304
|
/**
|
|
1305
|
+
* Check if `inner` is fully contained within `outer`'s source range.
|
|
1306
|
+
* @param inner The node that may be contained.
|
|
1307
|
+
* @param outer The node that may contain it.
|
|
1308
|
+
*/
|
|
1309
|
+
function isNodeWithin(inner, outer) {
|
|
1310
|
+
return inner.range[0] >= outer.range[0] && inner.range[1] <= outer.range[1];
|
|
1311
|
+
}
|
|
1312
|
+
/**
|
|
1313
|
+
* Resolve an expression to the function it ultimately refers to, following
|
|
1314
|
+
* simple local aliasing (`const fn2 = fn;`) via scope resolution.
|
|
1315
|
+
* @param context The ESLint rule context.
|
|
1316
|
+
* @param node The expression to resolve.
|
|
1317
|
+
* @param seen Identifiers already visited, to guard against cycles.
|
|
1318
|
+
*/
|
|
1319
|
+
function resolveToFunctionNode(context, node, seen = /* @__PURE__ */ new Set()) {
|
|
1320
|
+
const expr = Extract.unwrap(node);
|
|
1321
|
+
if (Check.isFunction(expr)) return expr;
|
|
1322
|
+
if (expr.type !== AST_NODE_TYPES.Identifier) return null;
|
|
1323
|
+
if (seen.has(expr)) return null;
|
|
1324
|
+
seen.add(expr);
|
|
1325
|
+
const resolved = resolve(context, expr);
|
|
1326
|
+
if (resolved == null) return null;
|
|
1327
|
+
return resolveToFunctionNode(context, resolved, seen);
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1299
1330
|
* Check if a name is ref-like ("ref" or ends with "Ref").
|
|
1300
1331
|
* Refs are mutable by design and exempted from immutability checks.
|
|
1301
1332
|
* @param name The identifier name to check.
|
|
@@ -1317,25 +1348,6 @@ function hasRefLikeNameInChain(node) {
|
|
|
1317
1348
|
}
|
|
1318
1349
|
return false;
|
|
1319
1350
|
}
|
|
1320
|
-
/**
|
|
1321
|
-
* Check if `name` appears anywhere inside a parameter pattern.
|
|
1322
|
-
* @param pattern - The parameter pattern to search in.
|
|
1323
|
-
* @param name - The identifier name to look for.
|
|
1324
|
-
*/
|
|
1325
|
-
function identifierExistsInPattern(pattern, name) {
|
|
1326
|
-
switch (pattern.type) {
|
|
1327
|
-
case AST_NODE_TYPES.Identifier: return pattern.name === name;
|
|
1328
|
-
case AST_NODE_TYPES.ObjectPattern: return pattern.properties.some((p) => {
|
|
1329
|
-
if (p.type === AST_NODE_TYPES.Property) return identifierExistsInPattern(p.value, name);
|
|
1330
|
-
return identifierExistsInPattern(p.argument, name);
|
|
1331
|
-
});
|
|
1332
|
-
case AST_NODE_TYPES.ArrayPattern: return pattern.elements.some((el) => el != null && identifierExistsInPattern(el, name));
|
|
1333
|
-
case AST_NODE_TYPES.RestElement: return identifierExistsInPattern(pattern.argument, name);
|
|
1334
|
-
case AST_NODE_TYPES.AssignmentPattern: return identifierExistsInPattern(pattern.left, name);
|
|
1335
|
-
case AST_NODE_TYPES.MemberExpression: return Extract.getRootIdentifier(pattern)?.name === name;
|
|
1336
|
-
default: return false;
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
1351
|
|
|
1340
1352
|
//#endregion
|
|
1341
1353
|
//#region src/rules/immutability/immutability.ts
|
|
@@ -1343,11 +1355,10 @@ const RULE_NAME$48 = "immutability";
|
|
|
1343
1355
|
var immutability_default = createRule({
|
|
1344
1356
|
meta: {
|
|
1345
1357
|
type: "problem",
|
|
1346
|
-
docs: { description: "Validates against
|
|
1358
|
+
docs: { description: "Validates against passing functions that mutate captured local variables into frozen contexts such as JSX props, hook arguments, and hook return values." },
|
|
1347
1359
|
messages: {
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
noRefLikeStateName: "State variable '{{name}}' should not be named 'ref' or end with 'Ref'. Use a different name to avoid confusion with mutable refs."
|
|
1360
|
+
default: "This function may (indirectly) reassign or modify '{{name}}' after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.",
|
|
1361
|
+
mutates: "This modifies '{{name}}'."
|
|
1351
1362
|
},
|
|
1352
1363
|
schema: []
|
|
1353
1364
|
},
|
|
@@ -1356,166 +1367,117 @@ var immutability_default = createRule({
|
|
|
1356
1367
|
defaultOptions: []
|
|
1357
1368
|
});
|
|
1358
1369
|
function create$48(context) {
|
|
1359
|
-
const { additionalStateHooks } = getSettingsFromContext(context);
|
|
1360
1370
|
const hc = core.getHookCollector(context);
|
|
1361
|
-
const
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
* Return true when `id` is the *value* variable (index 0) produced by a
|
|
1372
|
-
* `useState(…)` or `useReducer(…)` call.
|
|
1373
|
-
* @param id The identifier to check. May be a reference to the state variable, e.g. used inside an event handler nested in the component body — scope resolution will trace it back to the original declaration.
|
|
1374
|
-
* @returns True if `id` is a state variable, false otherwise.
|
|
1375
|
-
*/
|
|
1376
|
-
function isStateValue(id) {
|
|
1377
|
-
const initNode = resolve(context, id);
|
|
1378
|
-
if (initNode == null || initNode.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
1379
|
-
if (!isUseStateCall(initNode) && !core.isUseReducerCall(context, initNode)) return false;
|
|
1380
|
-
const declarator = initNode.parent;
|
|
1381
|
-
if (!("id" in declarator) || declarator.id?.type !== AST_NODE_TYPES.ArrayPattern) return true;
|
|
1382
|
-
return declarator.id.elements.findIndex((el) => el?.type === AST_NODE_TYPES.Identifier && el.name === id.name) === 0;
|
|
1371
|
+
const mutationSites = [];
|
|
1372
|
+
const sinkCandidates = [];
|
|
1373
|
+
function pushMutationSite(node, root) {
|
|
1374
|
+
const enclosing = Traverse.findParent(node, Check.isFunction);
|
|
1375
|
+
if (enclosing == null) return;
|
|
1376
|
+
mutationSites.push({
|
|
1377
|
+
enclosing,
|
|
1378
|
+
node,
|
|
1379
|
+
root
|
|
1380
|
+
});
|
|
1383
1381
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
* }
|
|
1400
|
-
* @param id The identifier to check.
|
|
1401
|
-
* @returns The function node where `id` is a parameter, or `null`.
|
|
1402
|
-
*/
|
|
1403
|
-
function getPropsDefiningFunction(id) {
|
|
1404
|
-
const variable = findVariable(context.sourceCode.getScope(id), id);
|
|
1405
|
-
if (variable == null) return null;
|
|
1406
|
-
for (const def of variable.defs) {
|
|
1407
|
-
if (def.type !== DefinitionType.Parameter) continue;
|
|
1408
|
-
let fn = def.node;
|
|
1409
|
-
for (;;) {
|
|
1410
|
-
if (Check.isFunction(fn)) break;
|
|
1411
|
-
fn = fn.parent ?? null;
|
|
1412
|
-
if (fn == null) break;
|
|
1382
|
+
return merge(hc.visitor, {
|
|
1383
|
+
AssignmentExpression(node) {
|
|
1384
|
+
const left = Extract.unwrap(node.left);
|
|
1385
|
+
switch (left.type) {
|
|
1386
|
+
case AST_NODE_TYPES.Identifier:
|
|
1387
|
+
if (isRefLikeName(left.name)) return;
|
|
1388
|
+
pushMutationSite(node, left);
|
|
1389
|
+
return;
|
|
1390
|
+
case AST_NODE_TYPES.MemberExpression: {
|
|
1391
|
+
if (hasRefLikeNameInChain(left)) return;
|
|
1392
|
+
const rootId = Extract.getRootIdentifier(left);
|
|
1393
|
+
if (rootId == null) return;
|
|
1394
|
+
pushMutationSite(node, rootId);
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1413
1397
|
}
|
|
1414
|
-
|
|
1415
|
-
const func = fn;
|
|
1416
|
-
if (func.params.some((param) => identifierExistsInPattern(param, id.name))) return func;
|
|
1417
|
-
}
|
|
1418
|
-
return null;
|
|
1419
|
-
}
|
|
1420
|
-
return merge(hc.visitor, fc.visitor, {
|
|
1421
|
-
/**
|
|
1422
|
-
* Detect `state.push(…)`, `state.sort()`, etc.
|
|
1423
|
-
*
|
|
1424
|
-
* Pattern:
|
|
1425
|
-
* CallExpression
|
|
1426
|
-
* callee: MemberExpression
|
|
1427
|
-
* object: <state or props identifier>
|
|
1428
|
-
* property: <mutating method name>
|
|
1429
|
-
* @param node The CallExpression node to analyze.
|
|
1430
|
-
*/
|
|
1398
|
+
},
|
|
1431
1399
|
CallExpression(node) {
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
messageId: "noRefLikeStateName",
|
|
1439
|
-
node: firstElement
|
|
1440
|
-
});
|
|
1400
|
+
const callee = Extract.unwrap(node.callee);
|
|
1401
|
+
if (callee.type === AST_NODE_TYPES.MemberExpression) {
|
|
1402
|
+
const propName = Extract.getPropertyName(callee.property);
|
|
1403
|
+
if (propName != null && MUTATING_METHODS.has(propName) && !hasRefLikeNameInChain(callee.object)) {
|
|
1404
|
+
const rootId = Extract.getRootIdentifier(callee.object);
|
|
1405
|
+
if (rootId != null) pushMutationSite(node, rootId);
|
|
1441
1406
|
}
|
|
1442
1407
|
}
|
|
1443
|
-
const
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
if (!MUTATING_ARRAY_METHODS.has(property.name)) return;
|
|
1448
|
-
const rootId = Extract.getRootIdentifier(object);
|
|
1449
|
-
if (rootId == null) return;
|
|
1450
|
-
if (rootId.name === "draft") return;
|
|
1451
|
-
if (hasRefLikeNameInChain(object)) return;
|
|
1452
|
-
const enclosingFn = Traverse.findParent(node, Check.isFunction);
|
|
1453
|
-
if (enclosingFn == null) return;
|
|
1454
|
-
const isState = isStateValue(rootId);
|
|
1455
|
-
const propsDefiningFunc = !isState ? getPropsDefiningFunction(rootId) : null;
|
|
1456
|
-
if (!isState && propsDefiningFunc == null) return;
|
|
1457
|
-
violations.push({
|
|
1458
|
-
data: {
|
|
1459
|
-
name: context.sourceCode.getText(object),
|
|
1460
|
-
method: property.name
|
|
1461
|
-
},
|
|
1462
|
-
func: enclosingFn,
|
|
1463
|
-
messageId: "mutatingArrayMethod",
|
|
1464
|
-
node,
|
|
1465
|
-
...propsDefiningFunc != null ? { propsDefiningFunc } : {}
|
|
1466
|
-
});
|
|
1408
|
+
if (core.isHookCall(node)) for (const arg of node.arguments) {
|
|
1409
|
+
if (arg.type === AST_NODE_TYPES.SpreadElement) continue;
|
|
1410
|
+
sinkCandidates.push(arg);
|
|
1411
|
+
}
|
|
1467
1412
|
},
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
* left: MemberExpression
|
|
1474
|
-
* object: <state or props identifier (or deeper chain)>
|
|
1475
|
-
* @param node The AssignmentExpression node to analyze.
|
|
1476
|
-
*/
|
|
1477
|
-
AssignmentExpression(node) {
|
|
1478
|
-
if (node.left.type !== AST_NODE_TYPES.MemberExpression) return;
|
|
1479
|
-
const rootId = Extract.getRootIdentifier(node.left);
|
|
1480
|
-
if (rootId == null) return;
|
|
1481
|
-
if (rootId.name === "draft") return;
|
|
1482
|
-
if (hasRefLikeNameInChain(node.left.object)) return;
|
|
1483
|
-
const enclosingFn = Traverse.findParent(node, Check.isFunction);
|
|
1484
|
-
if (enclosingFn == null) return;
|
|
1485
|
-
const isState = isStateValue(rootId);
|
|
1486
|
-
const propsDefiningFunc = !isState ? getPropsDefiningFunction(rootId) : null;
|
|
1487
|
-
if (!isState && propsDefiningFunc == null) return;
|
|
1488
|
-
violations.push({
|
|
1489
|
-
data: { name: context.sourceCode.getText(node.left.object) },
|
|
1490
|
-
func: enclosingFn,
|
|
1491
|
-
messageId: "mutatingAssignment",
|
|
1492
|
-
node,
|
|
1493
|
-
...propsDefiningFunc != null ? { propsDefiningFunc } : {}
|
|
1494
|
-
});
|
|
1413
|
+
JSXAttribute(node) {
|
|
1414
|
+
if (node.value?.type !== AST_NODE_TYPES.JSXExpressionContainer) return;
|
|
1415
|
+
const expr = node.value.expression;
|
|
1416
|
+
if (expr.type === AST_NODE_TYPES.JSXEmptyExpression) return;
|
|
1417
|
+
sinkCandidates.push(expr);
|
|
1495
1418
|
},
|
|
1496
|
-
"Program:exit"(
|
|
1497
|
-
const
|
|
1498
|
-
const
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
let
|
|
1419
|
+
"Program:exit"(program) {
|
|
1420
|
+
const mutableFunctions = /* @__PURE__ */ new Map();
|
|
1421
|
+
for (const { enclosing, node, root } of mutationSites) {
|
|
1422
|
+
const variable = findVariable(context.sourceCode.getScope(root), root);
|
|
1423
|
+
if (variable == null) continue;
|
|
1424
|
+
const declId = variable.identifiers.at(0) ?? null;
|
|
1425
|
+
let current = enclosing;
|
|
1503
1426
|
while (current != null) {
|
|
1504
|
-
if (
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1427
|
+
if (declId != null && isNodeWithin(declId, current)) break;
|
|
1428
|
+
if (!mutableFunctions.has(current)) mutableFunctions.set(current, {
|
|
1429
|
+
name: variable.name,
|
|
1430
|
+
node
|
|
1431
|
+
});
|
|
1508
1432
|
current = Traverse.findParent(current, Check.isFunction);
|
|
1509
1433
|
}
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1434
|
+
}
|
|
1435
|
+
if (mutableFunctions.size === 0) return;
|
|
1436
|
+
const reported = /* @__PURE__ */ new Set();
|
|
1437
|
+
function checkSink(expr) {
|
|
1438
|
+
if (expr == null || reported.has(expr)) return;
|
|
1439
|
+
const fn = resolveToFunctionNode(context, expr);
|
|
1440
|
+
if (fn == null) return;
|
|
1441
|
+
const mutation = mutableFunctions.get(fn);
|
|
1442
|
+
if (mutation == null) return;
|
|
1443
|
+
reported.add(expr);
|
|
1514
1444
|
context.report({
|
|
1515
|
-
data,
|
|
1516
|
-
messageId,
|
|
1517
|
-
node
|
|
1445
|
+
data: { name: mutation.name },
|
|
1446
|
+
messageId: "default",
|
|
1447
|
+
node: expr
|
|
1518
1448
|
});
|
|
1449
|
+
context.report({
|
|
1450
|
+
data: { name: mutation.name },
|
|
1451
|
+
messageId: "mutates",
|
|
1452
|
+
node: mutation.node
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
for (const candidate of sinkCandidates) checkSink(candidate);
|
|
1456
|
+
for (const hook of hc.api.getAllHooks(program)) for (const ret of hook.rets) checkSink(ret);
|
|
1457
|
+
},
|
|
1458
|
+
UnaryExpression(node) {
|
|
1459
|
+
if (node.operator !== "delete") return;
|
|
1460
|
+
const arg = Extract.unwrap(node.argument);
|
|
1461
|
+
if (arg.type !== AST_NODE_TYPES.MemberExpression) return;
|
|
1462
|
+
if (hasRefLikeNameInChain(arg)) return;
|
|
1463
|
+
const rootId = Extract.getRootIdentifier(arg);
|
|
1464
|
+
if (rootId == null) return;
|
|
1465
|
+
pushMutationSite(node, rootId);
|
|
1466
|
+
},
|
|
1467
|
+
UpdateExpression(node) {
|
|
1468
|
+
const arg = Extract.unwrap(node.argument);
|
|
1469
|
+
switch (arg.type) {
|
|
1470
|
+
case AST_NODE_TYPES.Identifier:
|
|
1471
|
+
if (isRefLikeName(arg.name)) return;
|
|
1472
|
+
pushMutationSite(node, arg);
|
|
1473
|
+
return;
|
|
1474
|
+
case AST_NODE_TYPES.MemberExpression: {
|
|
1475
|
+
if (hasRefLikeNameInChain(arg)) return;
|
|
1476
|
+
const rootId = Extract.getRootIdentifier(arg);
|
|
1477
|
+
if (rootId == null) return;
|
|
1478
|
+
pushMutationSite(node, rootId);
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1519
1481
|
}
|
|
1520
1482
|
}
|
|
1521
1483
|
});
|
|
@@ -2678,19 +2640,19 @@ function create$27(context) {
|
|
|
2678
2640
|
/**
|
|
2679
2641
|
* Recursively inspects a node to find potential leaked conditional rendering
|
|
2680
2642
|
* @param node The AST node to inspect
|
|
2681
|
-
* @param
|
|
2643
|
+
* @param seen A set of visited identifier names to prevent infinite recursion.
|
|
2682
2644
|
* @returns A report descriptor if a problem is found, otherwise `null`
|
|
2683
2645
|
*/
|
|
2684
|
-
function getReportDescriptor(node,
|
|
2646
|
+
function getReportDescriptor(node, seen = /* @__PURE__ */ new Set()) {
|
|
2685
2647
|
if (node == null) return null;
|
|
2686
|
-
if (Check.is(AST_NODE_TYPES.JSXExpressionContainer)(node)) return getReportDescriptor(node.expression,
|
|
2648
|
+
if (Check.is(AST_NODE_TYPES.JSXExpressionContainer)(node)) return getReportDescriptor(node.expression, seen);
|
|
2687
2649
|
if (Check.isJSX(node)) return null;
|
|
2688
|
-
if (Check.isTypeExpression(node)) return getReportDescriptor(node.expression,
|
|
2650
|
+
if (Check.isTypeExpression(node)) return getReportDescriptor(node.expression, seen);
|
|
2689
2651
|
return match(node).with({
|
|
2690
2652
|
type: AST_NODE_TYPES.LogicalExpression,
|
|
2691
2653
|
operator: "&&"
|
|
2692
2654
|
}, ({ left, right }) => {
|
|
2693
|
-
if (left.type === AST_NODE_TYPES.UnaryExpression && left.operator === "!") return getReportDescriptor(right,
|
|
2655
|
+
if (left.type === AST_NODE_TYPES.UnaryExpression && left.operator === "!") return getReportDescriptor(right, seen);
|
|
2694
2656
|
const initialScope = context.sourceCode.getScope(left);
|
|
2695
2657
|
if (Check.isIdentifier(left, "NaN") || getStaticValue(left, initialScope)?.value === "NaN") return {
|
|
2696
2658
|
data: { value: context.sourceCode.getText(left) },
|
|
@@ -2699,19 +2661,19 @@ function create$27(context) {
|
|
|
2699
2661
|
};
|
|
2700
2662
|
const leftType = getConstrainedTypeAtLocation(services, left);
|
|
2701
2663
|
const leftTypeVariants = core.getTypeVariants(unionConstituents(leftType));
|
|
2702
|
-
if (Array.from(leftTypeVariants.values()).every((t) => allowedTypeVariants.some((a) => a === t))) return getReportDescriptor(right,
|
|
2664
|
+
if (Array.from(leftTypeVariants.values()).every((t) => allowedTypeVariants.some((a) => a === t))) return getReportDescriptor(right, seen);
|
|
2703
2665
|
return {
|
|
2704
2666
|
data: { value: context.sourceCode.getText(left) },
|
|
2705
2667
|
messageId: "default",
|
|
2706
2668
|
node: left
|
|
2707
2669
|
};
|
|
2708
2670
|
}).with({ type: AST_NODE_TYPES.ConditionalExpression }, ({ alternate, consequent }) => {
|
|
2709
|
-
return getReportDescriptor(consequent,
|
|
2671
|
+
return getReportDescriptor(consequent, seen) ?? getReportDescriptor(alternate, seen);
|
|
2710
2672
|
}).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
|
|
2711
|
-
if (
|
|
2712
|
-
|
|
2673
|
+
if (seen.has(n.name)) return null;
|
|
2674
|
+
seen.add(n.name);
|
|
2713
2675
|
const variableDefNode = findVariable(context.sourceCode.getScope(n), n.name)?.defs.at(0)?.node;
|
|
2714
|
-
return match(variableDefNode).with({ init: P.select({ type: P.not(AST_NODE_TYPES.VariableDeclaration) }) }, (init) => getReportDescriptor(init,
|
|
2676
|
+
return match(variableDefNode).with({ init: P.select({ type: P.not(AST_NODE_TYPES.VariableDeclaration) }) }, (init) => getReportDescriptor(init, seen)).otherwise(() => null);
|
|
2715
2677
|
}).otherwise(() => null);
|
|
2716
2678
|
}
|
|
2717
2679
|
return { JSXExpressionContainer: flow(getReportDescriptor, report$1(context)) };
|
|
@@ -4207,11 +4169,11 @@ var purity_default = createRule({
|
|
|
4207
4169
|
* or resolves to a non-builtin source.
|
|
4208
4170
|
* @param context - The rule context.
|
|
4209
4171
|
* @param node - The identifier node to resolve.
|
|
4210
|
-
* @param
|
|
4172
|
+
* @param seen - A set of already visited identifier names to prevent infinite loops.
|
|
4211
4173
|
*/
|
|
4212
|
-
function resolveBuiltinObjectName(context, node,
|
|
4213
|
-
if (
|
|
4214
|
-
|
|
4174
|
+
function resolveBuiltinObjectName(context, node, seen = /* @__PURE__ */ new Set()) {
|
|
4175
|
+
if (seen.has(node.name)) return null;
|
|
4176
|
+
seen.add(node.name);
|
|
4215
4177
|
const variable = findVariable(context.sourceCode.getScope(node), node);
|
|
4216
4178
|
if (variable == null) return node.name;
|
|
4217
4179
|
const def = variable.defs[0];
|
|
@@ -4219,10 +4181,10 @@ function resolveBuiltinObjectName(context, node, visited = /* @__PURE__ */ new S
|
|
|
4219
4181
|
if (def.type === DefinitionType.ImplicitGlobalVariable) return node.name;
|
|
4220
4182
|
if (def.type === DefinitionType.Variable && def.node.init != null) {
|
|
4221
4183
|
const init = Extract.unwrap(def.node.init);
|
|
4222
|
-
if (init.type === AST_NODE_TYPES.Identifier) return resolveBuiltinObjectName(context, init,
|
|
4184
|
+
if (init.type === AST_NODE_TYPES.Identifier) return resolveBuiltinObjectName(context, init, seen);
|
|
4223
4185
|
if (init.type === AST_NODE_TYPES.MemberExpression) {
|
|
4224
4186
|
const rootId = Extract.getRootIdentifier(init);
|
|
4225
|
-
if (rootId != null) return resolveBuiltinObjectName(context, rootId,
|
|
4187
|
+
if (rootId != null) return resolveBuiltinObjectName(context, rootId, seen);
|
|
4226
4188
|
}
|
|
4227
4189
|
}
|
|
4228
4190
|
return null;
|
|
@@ -4395,9 +4357,9 @@ var refs_default = createRule({
|
|
|
4395
4357
|
defaultOptions: []
|
|
4396
4358
|
});
|
|
4397
4359
|
function resolveAlias(name, aliases) {
|
|
4398
|
-
const
|
|
4399
|
-
while (aliases.has(name) && !
|
|
4400
|
-
|
|
4360
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4361
|
+
while (aliases.has(name) && !seen.has(name)) {
|
|
4362
|
+
seen.add(name);
|
|
4401
4363
|
name = aliases.get(name);
|
|
4402
4364
|
}
|
|
4403
4365
|
return name;
|
|
@@ -4544,7 +4506,7 @@ function create$6(context) {
|
|
|
4544
4506
|
let matchedGuardIf = false;
|
|
4545
4507
|
if (!isLazyInit && refName != null) {
|
|
4546
4508
|
let current = node.parent;
|
|
4547
|
-
|
|
4509
|
+
findLoop: while (true) {
|
|
4548
4510
|
if (current.type === AST_NODE_TYPES.IfStatement) {
|
|
4549
4511
|
if (isRefCurrentNullCheck(current.test, refName)) {
|
|
4550
4512
|
isLazyInit = true;
|
|
@@ -4566,7 +4528,7 @@ function create$6(context) {
|
|
|
4566
4528
|
case AST_NODE_TYPES.MemberExpression:
|
|
4567
4529
|
case AST_NODE_TYPES.ChainExpression:
|
|
4568
4530
|
case AST_NODE_TYPES.CallExpression: break;
|
|
4569
|
-
default: break
|
|
4531
|
+
default: break findLoop;
|
|
4570
4532
|
}
|
|
4571
4533
|
current = current.parent;
|
|
4572
4534
|
}
|
|
@@ -5847,7 +5809,7 @@ var CodePath = class {
|
|
|
5847
5809
|
let index = 0;
|
|
5848
5810
|
let end = 0;
|
|
5849
5811
|
let segment = null;
|
|
5850
|
-
const
|
|
5812
|
+
const seen = Object.create(null);
|
|
5851
5813
|
const stack = [[startSegment, 0]];
|
|
5852
5814
|
let skippedSegment = null;
|
|
5853
5815
|
let broken = false;
|
|
@@ -5861,14 +5823,14 @@ var CodePath = class {
|
|
|
5861
5823
|
}
|
|
5862
5824
|
};
|
|
5863
5825
|
function isVisited(prevSegment) {
|
|
5864
|
-
return
|
|
5826
|
+
return seen[prevSegment.id] || segment.isLoopedPrevSegment(prevSegment);
|
|
5865
5827
|
}
|
|
5866
5828
|
while (stack.length > 0) {
|
|
5867
5829
|
item = stack[stack.length - 1];
|
|
5868
5830
|
segment = item[0];
|
|
5869
5831
|
index = item[1];
|
|
5870
5832
|
if (index === 0) {
|
|
5871
|
-
if (
|
|
5833
|
+
if (seen[segment.id]) {
|
|
5872
5834
|
stack.pop();
|
|
5873
5835
|
continue;
|
|
5874
5836
|
}
|
|
@@ -5877,7 +5839,7 @@ var CodePath = class {
|
|
|
5877
5839
|
continue;
|
|
5878
5840
|
}
|
|
5879
5841
|
if (skippedSegment && segment.prevSegments.includes(skippedSegment)) skippedSegment = null;
|
|
5880
|
-
|
|
5842
|
+
seen[segment.id] = true;
|
|
5881
5843
|
if (!skippedSegment) {
|
|
5882
5844
|
resolvedCallback.call(this, segment, controller);
|
|
5883
5845
|
if (segment === lastSegment) controller.skip();
|
|
@@ -6866,9 +6828,9 @@ function isHookDecl(node) {
|
|
|
6866
6828
|
default: return false;
|
|
6867
6829
|
}
|
|
6868
6830
|
}
|
|
6869
|
-
function isInitializedFromRef(context, name, initialScope,
|
|
6870
|
-
if (
|
|
6871
|
-
|
|
6831
|
+
function isInitializedFromRef(context, name, initialScope, seen = /* @__PURE__ */ new Set()) {
|
|
6832
|
+
if (seen.has(name)) return false;
|
|
6833
|
+
seen.add(name);
|
|
6872
6834
|
for (const { node } of findVariable(initialScope, name)?.defs ?? []) {
|
|
6873
6835
|
if (node.type !== AST_NODE_TYPES.VariableDeclarator) continue;
|
|
6874
6836
|
const init = node.init;
|
|
@@ -6876,7 +6838,7 @@ function isInitializedFromRef(context, name, initialScope, visited = /* @__PURE_
|
|
|
6876
6838
|
switch (true) {
|
|
6877
6839
|
case init.type === AST_NODE_TYPES.MemberExpression && init.object.type === AST_NODE_TYPES.Identifier && (init.object.name === "ref" || init.object.name.endsWith("Ref")): return true;
|
|
6878
6840
|
case init.type === AST_NODE_TYPES.CallExpression && core.isUseRefCall(context, init): return true;
|
|
6879
|
-
case init.type === AST_NODE_TYPES.CallExpression: return getNestedIdentifiers(init).some((id) => isInitializedFromRef(context, id.name, context.sourceCode.getScope(id),
|
|
6841
|
+
case init.type === AST_NODE_TYPES.CallExpression: return getNestedIdentifiers(init).some((id) => isInitializedFromRef(context, id.name, context.sourceCode.getScope(id), seen));
|
|
6880
6842
|
}
|
|
6881
6843
|
}
|
|
6882
6844
|
return false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-react-x",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.12.0-beta.1",
|
|
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,12 +45,12 @@
|
|
|
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/
|
|
49
|
-
"@eslint-react/
|
|
50
|
-
"@eslint-react/
|
|
51
|
-
"@eslint-react/
|
|
52
|
-
"@eslint-react/
|
|
53
|
-
"@eslint-react/
|
|
48
|
+
"@eslint-react/eslint": "5.12.0-beta.1",
|
|
49
|
+
"@eslint-react/jsx": "5.12.0-beta.1",
|
|
50
|
+
"@eslint-react/shared": "5.12.0-beta.1",
|
|
51
|
+
"@eslint-react/core": "5.12.0-beta.1",
|
|
52
|
+
"@eslint-react/ast": "5.12.0-beta.1",
|
|
53
|
+
"@eslint-react/var": "5.12.0-beta.1"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/react": "^19.2.17",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"tsl": "^1.0.30",
|
|
64
64
|
"tsl-dx": "^0.13.2",
|
|
65
65
|
"typescript": "6.0.3",
|
|
66
|
-
"@local/
|
|
67
|
-
"@local/
|
|
66
|
+
"@local/eff": "0.0.0",
|
|
67
|
+
"@local/configs": "0.0.0"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
70
|
"eslint": "*",
|