eslint-plugin-react-x 5.13.1 → 5.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +0 -1
- package/dist/index.js +380 -302
- package/package.json +10 -10
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ESLint, Linter } from "eslint";
|
|
2
|
-
|
|
3
2
|
//#region src/index.d.ts
|
|
4
3
|
type ConfigName = "disable-experimental" | "disable-type-checked" | "disable-conflict-eslint-plugin-react" | "disable-conflict-eslint-plugin-react-hooks" | "recommended" | "recommended-type-checked" | "recommended-typescript" | "strict" | "strict-type-checked" | "strict-typescript";
|
|
5
4
|
declare const finalPlugin: ESLint.Plugin & {
|
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.14.0";
|
|
148
148
|
|
|
149
149
|
//#endregion
|
|
150
150
|
//#region src/utils/create-rule.ts
|
|
@@ -1331,7 +1331,7 @@ function resolveToFunctionNode(context, node, seen = /* @__PURE__ */ new Set())
|
|
|
1331
1331
|
* Refs are mutable by design and exempted from immutability checks.
|
|
1332
1332
|
* @param name The identifier name to check.
|
|
1333
1333
|
*/
|
|
1334
|
-
function isRefLikeName(name) {
|
|
1334
|
+
function isRefLikeName$1(name) {
|
|
1335
1335
|
return name === "ref" || name.endsWith("Ref");
|
|
1336
1336
|
}
|
|
1337
1337
|
/**
|
|
@@ -1340,14 +1340,41 @@ function isRefLikeName(name) {
|
|
|
1340
1340
|
* @param node The AST node to inspect.
|
|
1341
1341
|
*/
|
|
1342
1342
|
function hasRefLikeNameInChain(node) {
|
|
1343
|
-
if (node.type === AST_NODE_TYPES.Identifier) return isRefLikeName(node.name);
|
|
1343
|
+
if (node.type === AST_NODE_TYPES.Identifier) return isRefLikeName$1(node.name);
|
|
1344
1344
|
if (node.type === AST_NODE_TYPES.MemberExpression) {
|
|
1345
1345
|
const propName = Extract.getPropertyName(node.property);
|
|
1346
|
-
if (propName != null && isRefLikeName(propName)) return true;
|
|
1346
|
+
if (propName != null && isRefLikeName$1(propName)) return true;
|
|
1347
1347
|
return hasRefLikeNameInChain(node.object);
|
|
1348
1348
|
}
|
|
1349
1349
|
return false;
|
|
1350
1350
|
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Check if the root identifier of a member-expression chain (or the
|
|
1353
|
+
* identifier itself) is initialized directly from a `useRef()` call, e.g.
|
|
1354
|
+
* `const mounted = useRef(false); mounted.current = true;`.
|
|
1355
|
+
*
|
|
1356
|
+
* This catches refs regardless of naming convention, complementing
|
|
1357
|
+
* {@link hasRefLikeNameInChain}.
|
|
1358
|
+
* @param context The ESLint rule context.
|
|
1359
|
+
* @param node The AST node to inspect (an identifier or member-expression chain).
|
|
1360
|
+
*/
|
|
1361
|
+
function isInitializedFromUseRef(context, node) {
|
|
1362
|
+
const rootId = node.type === AST_NODE_TYPES.Identifier ? node : Extract.getRootIdentifier(node);
|
|
1363
|
+
if (rootId == null) return false;
|
|
1364
|
+
const initNode = resolve(context, rootId);
|
|
1365
|
+
return initNode != null && initNode.type === AST_NODE_TYPES.CallExpression && core.isUseRefCall(context, initNode);
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Check if a mutated expression chain should be exempt from immutability
|
|
1369
|
+
* checks because it is rooted at a ref: either by naming convention
|
|
1370
|
+
* ({@link hasRefLikeNameInChain}) or because it is initialized from a
|
|
1371
|
+
* `useRef()` call ({@link isInitializedFromUseRef}).
|
|
1372
|
+
* @param context The ESLint rule context.
|
|
1373
|
+
* @param node The AST node to inspect (an identifier or member-expression chain).
|
|
1374
|
+
*/
|
|
1375
|
+
function isRefLikeChain(context, node) {
|
|
1376
|
+
return hasRefLikeNameInChain(node) || isInitializedFromUseRef(context, node);
|
|
1377
|
+
}
|
|
1351
1378
|
|
|
1352
1379
|
//#endregion
|
|
1353
1380
|
//#region src/rules/immutability/immutability.ts
|
|
@@ -1384,11 +1411,11 @@ function create$48(context) {
|
|
|
1384
1411
|
const left = Extract.unwrap(node.left);
|
|
1385
1412
|
switch (left.type) {
|
|
1386
1413
|
case AST_NODE_TYPES.Identifier:
|
|
1387
|
-
if (isRefLikeName(left.name)) return;
|
|
1414
|
+
if (isRefLikeName$1(left.name)) return;
|
|
1388
1415
|
pushMutationSite(node, left);
|
|
1389
1416
|
return;
|
|
1390
1417
|
case AST_NODE_TYPES.MemberExpression: {
|
|
1391
|
-
if (
|
|
1418
|
+
if (isRefLikeChain(context, left)) return;
|
|
1392
1419
|
const rootId = Extract.getRootIdentifier(left);
|
|
1393
1420
|
if (rootId == null) return;
|
|
1394
1421
|
pushMutationSite(node, rootId);
|
|
@@ -1400,7 +1427,7 @@ function create$48(context) {
|
|
|
1400
1427
|
const callee = Extract.unwrap(node.callee);
|
|
1401
1428
|
if (callee.type === AST_NODE_TYPES.MemberExpression) {
|
|
1402
1429
|
const propName = Extract.getPropertyName(callee.property);
|
|
1403
|
-
if (propName != null && MUTATING_METHODS.has(propName) && !
|
|
1430
|
+
if (propName != null && MUTATING_METHODS.has(propName) && !isRefLikeChain(context, callee.object)) {
|
|
1404
1431
|
const rootId = Extract.getRootIdentifier(callee.object);
|
|
1405
1432
|
if (rootId != null) pushMutationSite(node, rootId);
|
|
1406
1433
|
}
|
|
@@ -1459,7 +1486,7 @@ function create$48(context) {
|
|
|
1459
1486
|
if (node.operator !== "delete") return;
|
|
1460
1487
|
const arg = Extract.unwrap(node.argument);
|
|
1461
1488
|
if (arg.type !== AST_NODE_TYPES.MemberExpression) return;
|
|
1462
|
-
if (
|
|
1489
|
+
if (isRefLikeChain(context, arg)) return;
|
|
1463
1490
|
const rootId = Extract.getRootIdentifier(arg);
|
|
1464
1491
|
if (rootId == null) return;
|
|
1465
1492
|
pushMutationSite(node, rootId);
|
|
@@ -1468,11 +1495,11 @@ function create$48(context) {
|
|
|
1468
1495
|
const arg = Extract.unwrap(node.argument);
|
|
1469
1496
|
switch (arg.type) {
|
|
1470
1497
|
case AST_NODE_TYPES.Identifier:
|
|
1471
|
-
if (isRefLikeName(arg.name)) return;
|
|
1498
|
+
if (isRefLikeName$1(arg.name)) return;
|
|
1472
1499
|
pushMutationSite(node, arg);
|
|
1473
1500
|
return;
|
|
1474
1501
|
case AST_NODE_TYPES.MemberExpression: {
|
|
1475
|
-
if (
|
|
1502
|
+
if (isRefLikeChain(context, arg)) return;
|
|
1476
1503
|
const rootId = Extract.getRootIdentifier(arg);
|
|
1477
1504
|
if (rootId == null) return;
|
|
1478
1505
|
pushMutationSite(node, rootId);
|
|
@@ -4239,83 +4266,64 @@ function create$7(context) {
|
|
|
4239
4266
|
|
|
4240
4267
|
//#endregion
|
|
4241
4268
|
//#region src/rules/refs/lib.ts
|
|
4269
|
+
const SYNC_ARRAY_CALLBACKS = /* @__PURE__ */ new Set([
|
|
4270
|
+
"every",
|
|
4271
|
+
"filter",
|
|
4272
|
+
"find",
|
|
4273
|
+
"findIndex",
|
|
4274
|
+
"findLast",
|
|
4275
|
+
"findLastIndex",
|
|
4276
|
+
"flatMap",
|
|
4277
|
+
"forEach",
|
|
4278
|
+
"map",
|
|
4279
|
+
"reduce",
|
|
4280
|
+
"reduceRight",
|
|
4281
|
+
"some",
|
|
4282
|
+
"sort"
|
|
4283
|
+
]);
|
|
4284
|
+
function getLatestValue(events, position) {
|
|
4285
|
+
let latest = null;
|
|
4286
|
+
for (const event of events ?? []) {
|
|
4287
|
+
if (event.position > position) continue;
|
|
4288
|
+
if (latest == null || event.position >= latest.position) latest = event;
|
|
4289
|
+
}
|
|
4290
|
+
return latest;
|
|
4291
|
+
}
|
|
4242
4292
|
function isFunctionExpressionLike(node) {
|
|
4243
4293
|
return node.type === AST_NODE_TYPES.FunctionExpression || node.type === AST_NODE_TYPES.ArrowFunctionExpression;
|
|
4244
4294
|
}
|
|
4245
|
-
function
|
|
4246
|
-
|
|
4247
|
-
while (aliases.has(name) && !seen.has(name)) {
|
|
4248
|
-
seen.add(name);
|
|
4249
|
-
name = aliases.get(name);
|
|
4250
|
-
}
|
|
4251
|
-
return name;
|
|
4252
|
-
}
|
|
4253
|
-
/**
|
|
4254
|
-
* Check if the node is the operand of a `ref.current === null` test inside an IfStatement.
|
|
4255
|
-
* @param node The MemberExpression node for ref.current
|
|
4256
|
-
* @returns true if the node is part of a null check test in an if statement
|
|
4257
|
-
*/
|
|
4258
|
-
function isInNullCheckTest(node) {
|
|
4259
|
-
let parent = node.parent;
|
|
4260
|
-
while (Check.isTypeExpression(parent)) parent = parent.parent;
|
|
4261
|
-
const isNullCompareBinary = (n) => {
|
|
4262
|
-
return n.type === AST_NODE_TYPES.BinaryExpression && /^(===|==|!==|!=)$/.test(n.operator);
|
|
4263
|
-
};
|
|
4264
|
-
const isLiteralNull = (n) => {
|
|
4265
|
-
return n.type === AST_NODE_TYPES.Literal && n.value == null;
|
|
4266
|
-
};
|
|
4267
|
-
const isIfTest = (testNode) => {
|
|
4268
|
-
return testNode.parent?.type === AST_NODE_TYPES.IfStatement && testNode.parent.test === testNode;
|
|
4269
|
-
};
|
|
4270
|
-
if (isNullCompareBinary(parent)) {
|
|
4271
|
-
if (!isLiteralNull(parent.left === node || Extract.unwrap(parent.left) === node ? parent.right : parent.left)) return false;
|
|
4272
|
-
if (isIfTest(parent)) return true;
|
|
4273
|
-
const grandparent = parent.parent;
|
|
4274
|
-
if (grandparent.type === AST_NODE_TYPES.UnaryExpression && grandparent.operator === "!" && isIfTest(grandparent)) return true;
|
|
4275
|
-
return false;
|
|
4276
|
-
}
|
|
4277
|
-
if (parent.type === AST_NODE_TYPES.UnaryExpression && parent.operator === "!") return isIfTest(parent);
|
|
4278
|
-
return false;
|
|
4295
|
+
function isRefLikeName(name) {
|
|
4296
|
+
return name === "ref" || name.endsWith("Ref");
|
|
4279
4297
|
}
|
|
4280
|
-
function
|
|
4281
|
-
|
|
4282
|
-
if (
|
|
4283
|
-
if (Extract.getPropertyName(
|
|
4284
|
-
|
|
4285
|
-
return obj.type === AST_NODE_TYPES.Identifier && obj.name === refName && b.type === AST_NODE_TYPES.Literal && b.value == null;
|
|
4298
|
+
function getCalleeName(node) {
|
|
4299
|
+
const callee = Extract.unwrap(node.callee);
|
|
4300
|
+
if (callee.type === AST_NODE_TYPES.Identifier) return callee.name;
|
|
4301
|
+
if (callee.type === AST_NODE_TYPES.MemberExpression) return Extract.getPropertyName(callee.property);
|
|
4302
|
+
return null;
|
|
4286
4303
|
}
|
|
4287
|
-
function
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4304
|
+
function getSynchronousCallbackIndexes(node) {
|
|
4305
|
+
const callee = Extract.unwrap(node.callee);
|
|
4306
|
+
const name = getCalleeName(node);
|
|
4307
|
+
if (name == null) return [];
|
|
4308
|
+
if (callee.type === AST_NODE_TYPES.MemberExpression && SYNC_ARRAY_CALLBACKS.has(name)) return [0];
|
|
4309
|
+
switch (name) {
|
|
4310
|
+
case "useMemo":
|
|
4311
|
+
case "useState": return [0];
|
|
4312
|
+
case "useReducer": return [0, 2];
|
|
4313
|
+
default: return [];
|
|
4314
|
+
}
|
|
4292
4315
|
}
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
*/
|
|
4299
|
-
function getRefCurrentNullCheckBranch(test, refName) {
|
|
4300
|
-
if (isBinaryNullCheck(test, refName)) return test.operator === "===" || test.operator === "==" ? "consequent" : "alternate";
|
|
4301
|
-
if (test.type === AST_NODE_TYPES.UnaryExpression && test.operator === "!") {
|
|
4302
|
-
const arg = Extract.unwrap(test.argument);
|
|
4303
|
-
if (arg.type === AST_NODE_TYPES.MemberExpression) {
|
|
4304
|
-
const obj = Extract.unwrap(arg.object);
|
|
4305
|
-
return obj.type === AST_NODE_TYPES.Identifier && obj.name === refName && Extract.getPropertyName(arg.property) === "current" ? "consequent" : null;
|
|
4306
|
-
}
|
|
4307
|
-
if (arg.type === AST_NODE_TYPES.BinaryExpression) {
|
|
4308
|
-
const inner = getRefCurrentNullCheckBranch(arg, refName);
|
|
4309
|
-
if (inner === "consequent") return "alternate";
|
|
4310
|
-
if (inner === "alternate") return "consequent";
|
|
4311
|
-
}
|
|
4316
|
+
function isReachedThroughFunctions(node, boundary, reached) {
|
|
4317
|
+
let current = node.parent;
|
|
4318
|
+
while (current != null && current !== boundary) {
|
|
4319
|
+
if (Check.isFunction(current) && !reached.has(current)) return false;
|
|
4320
|
+
current = current.parent;
|
|
4312
4321
|
}
|
|
4313
|
-
return
|
|
4322
|
+
return current === boundary;
|
|
4314
4323
|
}
|
|
4315
4324
|
/**
|
|
4316
4325
|
* Check whether `node` (a `ref.current` MemberExpression) is being written to indirectly through
|
|
4317
4326
|
* a nested property write, e.g. `ref.current.inner = value` or `ref.current.inner++`.
|
|
4318
|
-
* @param node The MemberExpression node for ref.current
|
|
4319
4327
|
*/
|
|
4320
4328
|
function isNestedRefCurrentWrite(node) {
|
|
4321
4329
|
let outer = node;
|
|
@@ -4334,19 +4342,87 @@ function isNestedRefCurrentWrite(node) {
|
|
|
4334
4342
|
return false;
|
|
4335
4343
|
}
|
|
4336
4344
|
}
|
|
4337
|
-
function
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4345
|
+
function getRefAccess(node) {
|
|
4346
|
+
let parent = node.parent;
|
|
4347
|
+
while (Check.isTypeExpression(parent)) parent = parent.parent;
|
|
4348
|
+
const isDirectWrite = parent.type === AST_NODE_TYPES.AssignmentExpression ? parent.left === node || Extract.unwrap(parent.left) === node : parent.type === AST_NODE_TYPES.UpdateExpression ? parent.argument === node || Extract.unwrap(parent.argument) === node : false;
|
|
4349
|
+
return {
|
|
4350
|
+
isInitializationWrite: parent.type === AST_NODE_TYPES.AssignmentExpression && parent.operator === "=" && (parent.left === node || Extract.unwrap(parent.left) === node),
|
|
4351
|
+
isWrite: isDirectWrite || isNestedRefCurrentWrite(node),
|
|
4352
|
+
node
|
|
4353
|
+
};
|
|
4354
|
+
}
|
|
4355
|
+
/**
|
|
4356
|
+
* Parse an exact nullish check and return the branch where the checked value is null/undefined.
|
|
4357
|
+
* Truthiness checks such as `!ref.current` are deliberately excluded: falsy ref values are not
|
|
4358
|
+
* necessarily uninitialized.
|
|
4359
|
+
*/
|
|
4360
|
+
function getNullCheckBranch(test, isCheckedValue, isNullishValue) {
|
|
4361
|
+
const unwrapped = Extract.unwrap(test);
|
|
4362
|
+
if (unwrapped.type === AST_NODE_TYPES.UnaryExpression && unwrapped.operator === "!") {
|
|
4363
|
+
const inner = Extract.unwrap(unwrapped.argument);
|
|
4364
|
+
if (inner.type !== AST_NODE_TYPES.BinaryExpression) return null;
|
|
4365
|
+
const branch = getNullCheckBranch(inner, isCheckedValue, isNullishValue);
|
|
4366
|
+
if (branch === "consequent") return "alternate";
|
|
4367
|
+
if (branch === "alternate") return "consequent";
|
|
4368
|
+
return null;
|
|
4369
|
+
}
|
|
4370
|
+
if (unwrapped.type !== AST_NODE_TYPES.BinaryExpression || !/^(===|==|!==|!=)$/.test(unwrapped.operator)) return null;
|
|
4371
|
+
const matches = (left, right) => {
|
|
4372
|
+
return isCheckedValue(Extract.unwrap(left)) && isNullishValue(Extract.unwrap(right));
|
|
4373
|
+
};
|
|
4374
|
+
if (!matches(unwrapped.left, unwrapped.right) && !matches(unwrapped.right, unwrapped.left)) return null;
|
|
4375
|
+
return unwrapped.operator === "===" || unwrapped.operator === "==" ? "consequent" : "alternate";
|
|
4376
|
+
}
|
|
4377
|
+
function isGuardTestAccess(node, getNullBranch) {
|
|
4378
|
+
let current = node;
|
|
4379
|
+
while (Check.isTypeExpression(current.parent)) current = current.parent;
|
|
4380
|
+
if (current.parent.type === AST_NODE_TYPES.BinaryExpression) current = current.parent;
|
|
4381
|
+
if (current.parent.type === AST_NODE_TYPES.UnaryExpression && current.parent.operator === "!") current = current.parent;
|
|
4382
|
+
return current.parent.type === AST_NODE_TYPES.IfStatement && current.parent.test === current && getNullBranch(current.parent.test) != null;
|
|
4383
|
+
}
|
|
4384
|
+
function getGuardDisposition(node, getNullBranch) {
|
|
4385
|
+
let child = node;
|
|
4386
|
+
let current = node.parent;
|
|
4387
|
+
while (current != null && !Check.isFunction(current)) {
|
|
4388
|
+
if (current.type === AST_NODE_TYPES.IfStatement) {
|
|
4389
|
+
const actualBranch = current.consequent === child ? "consequent" : current.alternate === child ? "alternate" : null;
|
|
4390
|
+
const nullBranch = getNullBranch(current.test);
|
|
4391
|
+
if (actualBranch != null && nullBranch != null) return { inNullBranch: actualBranch === nullBranch };
|
|
4392
|
+
}
|
|
4393
|
+
child = current;
|
|
4394
|
+
current = current.parent;
|
|
4395
|
+
}
|
|
4396
|
+
return null;
|
|
4397
|
+
}
|
|
4398
|
+
/** Return whether every path through a statement exits the current function. */
|
|
4399
|
+
function isUnconditionallyTerminating(statement) {
|
|
4400
|
+
switch (statement.type) {
|
|
4401
|
+
case AST_NODE_TYPES.ReturnStatement:
|
|
4402
|
+
case AST_NODE_TYPES.ThrowStatement: return true;
|
|
4403
|
+
case AST_NODE_TYPES.BlockStatement: {
|
|
4404
|
+
const last = statement.body.at(-1);
|
|
4405
|
+
return last != null && isUnconditionallyTerminating(last);
|
|
4349
4406
|
}
|
|
4407
|
+
case AST_NODE_TYPES.IfStatement: return statement.alternate != null && isUnconditionallyTerminating(statement.consequent) && isUnconditionallyTerminating(statement.alternate);
|
|
4408
|
+
default: return false;
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
function isAfterTerminatingNonNullGuard(node, getNullBranch) {
|
|
4412
|
+
let statement = node;
|
|
4413
|
+
while (statement.parent?.type !== AST_NODE_TYPES.BlockStatement) {
|
|
4414
|
+
if (statement.parent == null || Check.isFunction(statement.parent)) return false;
|
|
4415
|
+
statement = statement.parent;
|
|
4416
|
+
}
|
|
4417
|
+
const block = statement.parent;
|
|
4418
|
+
const index = block.body.indexOf(statement);
|
|
4419
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
4420
|
+
const sibling = block.body[i];
|
|
4421
|
+
if (sibling?.type !== AST_NODE_TYPES.IfStatement) continue;
|
|
4422
|
+
const nullBranch = getNullBranch(sibling.test);
|
|
4423
|
+
if (nullBranch == null) continue;
|
|
4424
|
+
const nonNullBranch = nullBranch === "alternate" ? sibling.consequent : sibling.alternate;
|
|
4425
|
+
return nonNullBranch != null && isUnconditionallyTerminating(nonNullBranch);
|
|
4350
4426
|
}
|
|
4351
4427
|
return false;
|
|
4352
4428
|
}
|
|
@@ -4370,251 +4446,253 @@ var refs_default = createRule({
|
|
|
4370
4446
|
create: create$6,
|
|
4371
4447
|
defaultOptions: []
|
|
4372
4448
|
});
|
|
4373
|
-
/**
|
|
4374
|
-
* Phase 2 (part): compute the set of functions that are "reached" during render for a given
|
|
4375
|
-
* component/hook boundary. A function is reached if it is the boundary itself, or if it is
|
|
4376
|
-
* directly called (possibly through a simple variable alias, e.g. `const b = a; b();`) from
|
|
4377
|
-
* somewhere that is itself reached. This lets us detect ref mutations/reads that happen inside
|
|
4378
|
-
* a helper function which is invoked synchronously during render (see "Ref Mutation in Called
|
|
4379
|
-
* Function" in refs.spec.md), as opposed to functions that are merely defined and handed off
|
|
4380
|
-
* to be called later (e.g. event handlers, effect callbacks).
|
|
4381
|
-
*/
|
|
4382
|
-
function computeReachedFunctions(boundary, isCompOrHookFn, functionVarBindings, directCallSites, aliases) {
|
|
4383
|
-
const reached = /* @__PURE__ */ new Set([boundary]);
|
|
4384
|
-
const relevantCalls = directCallSites.filter((c) => Traverse.findParent(c.node, isCompOrHookFn) === boundary);
|
|
4385
|
-
let changed = true;
|
|
4386
|
-
for (let iterations = 0; changed && iterations < 50; iterations++) {
|
|
4387
|
-
changed = false;
|
|
4388
|
-
for (const { calleeName, node: callNode } of relevantCalls) {
|
|
4389
|
-
const hostFn = Traverse.findParent(callNode, Check.isFunction) ?? boundary;
|
|
4390
|
-
if (!reached.has(hostFn)) continue;
|
|
4391
|
-
const resolvedName = resolveAlias(calleeName, aliases);
|
|
4392
|
-
const target = functionVarBindings.get(resolvedName);
|
|
4393
|
-
if (target == null || reached.has(target)) continue;
|
|
4394
|
-
if (Traverse.findParent(target, isCompOrHookFn) !== boundary) continue;
|
|
4395
|
-
reached.add(target);
|
|
4396
|
-
changed = true;
|
|
4397
|
-
}
|
|
4398
|
-
}
|
|
4399
|
-
return reached;
|
|
4400
|
-
}
|
|
4401
|
-
/**
|
|
4402
|
-
* Phase 2 (part): determine whether a ref access node is reached unconditionally during render,
|
|
4403
|
-
* i.e. every function boundary between the access and the component/hook `boundary` is itself
|
|
4404
|
-
* a function that gets invoked during render (see `computeReachedFunctions`).
|
|
4405
|
-
*/
|
|
4406
|
-
function isReachedDuringRender(node, boundary, reached, alwaysReached) {
|
|
4407
|
-
let current = node.parent;
|
|
4408
|
-
while (current != null && current !== boundary) {
|
|
4409
|
-
if (Check.isFunction(current) && !reached.has(current) && !alwaysReached.has(current)) return false;
|
|
4410
|
-
current = current.parent;
|
|
4411
|
-
}
|
|
4412
|
-
return true;
|
|
4413
|
-
}
|
|
4414
4449
|
function create$6(context) {
|
|
4415
4450
|
const hc = core.getHookCollector(context);
|
|
4416
4451
|
const fc = core.getFunctionComponentCollector(context);
|
|
4452
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
4453
|
+
const memberBindings = /* @__PURE__ */ new Map();
|
|
4454
|
+
const jsxRefs = /* @__PURE__ */ new Set();
|
|
4455
|
+
const calls = [];
|
|
4417
4456
|
const refAccesses = [];
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4457
|
+
function getVariable(node) {
|
|
4458
|
+
return findVariable(context.sourceCode.getScope(node), node) ?? null;
|
|
4459
|
+
}
|
|
4460
|
+
function addBinding(variable, value, position) {
|
|
4461
|
+
const events = bindings.get(variable) ?? [];
|
|
4462
|
+
bindings.set(variable, events);
|
|
4463
|
+
events.push({
|
|
4464
|
+
position,
|
|
4465
|
+
value
|
|
4466
|
+
});
|
|
4467
|
+
}
|
|
4468
|
+
function addIdentifierBinding(node, value, position = node.range[0]) {
|
|
4469
|
+
const variable = getVariable(node);
|
|
4470
|
+
if (variable != null) addBinding(variable, value, position);
|
|
4471
|
+
}
|
|
4472
|
+
function addMemberBinding(object, property, value, position) {
|
|
4473
|
+
const variable = getVariable(object);
|
|
4474
|
+
if (variable == null) return;
|
|
4475
|
+
const properties = memberBindings.get(variable) ?? /* @__PURE__ */ new Map();
|
|
4476
|
+
memberBindings.set(variable, properties);
|
|
4477
|
+
const events = properties.get(property) ?? [];
|
|
4478
|
+
properties.set(property, events);
|
|
4479
|
+
events.push({
|
|
4480
|
+
position,
|
|
4481
|
+
value
|
|
4482
|
+
});
|
|
4483
|
+
}
|
|
4484
|
+
function bindingValueFrom(node) {
|
|
4485
|
+
const value = Extract.unwrap(node);
|
|
4486
|
+
if (value.type === AST_NODE_TYPES.Identifier) {
|
|
4487
|
+
const variable = getVariable(value);
|
|
4488
|
+
return variable == null ? { kind: "unknown" } : {
|
|
4489
|
+
kind: "variable",
|
|
4490
|
+
variable
|
|
4491
|
+
};
|
|
4492
|
+
}
|
|
4493
|
+
if (isFunctionExpressionLike(value)) return {
|
|
4494
|
+
kind: "function",
|
|
4495
|
+
node: value
|
|
4496
|
+
};
|
|
4497
|
+
if (value.type === AST_NODE_TYPES.CallExpression && (core.isUseRefCall(context, value) || core.isCreateRefCall(context, value))) return { kind: "ref" };
|
|
4498
|
+
if (value.type === AST_NODE_TYPES.MemberExpression) {
|
|
4499
|
+
const property = Extract.getPropertyName(value.property);
|
|
4500
|
+
if (property != null && isRefLikeName(property)) return { kind: "ref" };
|
|
4501
|
+
}
|
|
4502
|
+
return { kind: "unknown" };
|
|
4503
|
+
}
|
|
4504
|
+
function resolveRef(variable, position, seen = /* @__PURE__ */ new Set()) {
|
|
4505
|
+
if (seen.has(variable)) return null;
|
|
4506
|
+
seen.add(variable);
|
|
4507
|
+
if (jsxRefs.has(variable) || isRefLikeName(variable.name)) return variable;
|
|
4508
|
+
const event = getLatestValue(bindings.get(variable), position);
|
|
4509
|
+
if (event != null) switch (event.value.kind) {
|
|
4510
|
+
case "ref": return variable;
|
|
4511
|
+
case "variable": return resolveRef(event.value.variable, event.position, seen);
|
|
4512
|
+
case "function":
|
|
4513
|
+
case "unknown": return null;
|
|
4514
|
+
}
|
|
4515
|
+
return null;
|
|
4516
|
+
}
|
|
4517
|
+
function resolveFunction(variable, position, seen = /* @__PURE__ */ new Set()) {
|
|
4518
|
+
if (seen.has(variable)) return null;
|
|
4519
|
+
seen.add(variable);
|
|
4520
|
+
const event = getLatestValue(bindings.get(variable), position);
|
|
4521
|
+
if (event == null) return null;
|
|
4522
|
+
switch (event.value.kind) {
|
|
4523
|
+
case "function": return event.value.node;
|
|
4524
|
+
case "variable": return resolveFunction(event.value.variable, event.position, seen);
|
|
4525
|
+
case "ref":
|
|
4526
|
+
case "unknown": return null;
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
function resolveCallable(node, position) {
|
|
4530
|
+
const callee = Extract.unwrap(node);
|
|
4531
|
+
if (isFunctionExpressionLike(callee)) return callee;
|
|
4532
|
+
if (callee.type === AST_NODE_TYPES.Identifier) {
|
|
4533
|
+
const variable = getVariable(callee);
|
|
4534
|
+
return variable == null ? null : resolveFunction(variable, position);
|
|
4535
|
+
}
|
|
4536
|
+
if (callee.type !== AST_NODE_TYPES.MemberExpression) return null;
|
|
4537
|
+
const object = Extract.unwrap(callee.object);
|
|
4538
|
+
const property = Extract.getPropertyName(callee.property);
|
|
4539
|
+
if (object.type !== AST_NODE_TYPES.Identifier || property == null) return null;
|
|
4540
|
+
const variable = getVariable(object);
|
|
4541
|
+
if (variable == null) return null;
|
|
4542
|
+
const event = getLatestValue(memberBindings.get(variable)?.get(property), position);
|
|
4543
|
+
if (event == null) return null;
|
|
4544
|
+
if (event.value.kind === "function") return event.value.node;
|
|
4545
|
+
if (event.value.kind === "variable") return resolveFunction(event.value.variable, event.position);
|
|
4546
|
+
return null;
|
|
4547
|
+
}
|
|
4548
|
+
function getRefTarget(node) {
|
|
4549
|
+
const object = Extract.unwrap(node.object);
|
|
4550
|
+
if (object.type === AST_NODE_TYPES.Identifier) {
|
|
4551
|
+
const variable = getVariable(object);
|
|
4552
|
+
if (variable == null) return null;
|
|
4553
|
+
const identity = resolveRef(variable, node.range[0]);
|
|
4554
|
+
return identity == null ? null : { identity };
|
|
4555
|
+
}
|
|
4556
|
+
if (object.type === AST_NODE_TYPES.MemberExpression) {
|
|
4557
|
+
const property = Extract.getPropertyName(object.property);
|
|
4558
|
+
if (property != null && isRefLikeName(property)) return { identity: null };
|
|
4559
|
+
}
|
|
4560
|
+
return null;
|
|
4561
|
+
}
|
|
4562
|
+
function getNullBranch(test, identity) {
|
|
4563
|
+
return getNullCheckBranch(test, (candidate) => {
|
|
4564
|
+
if (candidate.type !== AST_NODE_TYPES.MemberExpression || Extract.getPropertyName(candidate.property) !== "current") return false;
|
|
4565
|
+
return getRefTarget(candidate)?.identity === identity;
|
|
4566
|
+
}, (candidate) => {
|
|
4567
|
+
if (candidate.type === AST_NODE_TYPES.Literal) return candidate.value == null;
|
|
4568
|
+
if (candidate.type === AST_NODE_TYPES.UnaryExpression && candidate.operator === "void") return true;
|
|
4569
|
+
if (candidate.type !== AST_NODE_TYPES.Identifier || candidate.name !== "undefined") return false;
|
|
4570
|
+
const variable = getVariable(candidate);
|
|
4571
|
+
return variable == null || variable.defs.length === 0;
|
|
4572
|
+
});
|
|
4573
|
+
}
|
|
4424
4574
|
return merge(hc.visitor, fc.visitor, {
|
|
4425
4575
|
AssignmentExpression(node) {
|
|
4426
4576
|
if (node.operator !== "=") return;
|
|
4427
4577
|
const left = Extract.unwrap(node.left);
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
const leftObj = Extract.unwrap(left.object);
|
|
4431
|
-
const propName = Extract.getPropertyName(left.property);
|
|
4432
|
-
if (leftObj.type === AST_NODE_TYPES.Identifier && propName != null && isFunctionExpressionLike(right)) functionVarBindings.set(`${leftObj.name}.${propName}`, right);
|
|
4433
|
-
return;
|
|
4434
|
-
}
|
|
4435
|
-
if (left.type !== AST_NODE_TYPES.Identifier) return;
|
|
4436
|
-
if (right.type === AST_NODE_TYPES.Identifier) {
|
|
4437
|
-
aliases.set(left.name, right.name);
|
|
4578
|
+
if (left.type === AST_NODE_TYPES.Identifier) {
|
|
4579
|
+
addIdentifierBinding(left, bindingValueFrom(node.right), node.range[0]);
|
|
4438
4580
|
return;
|
|
4439
4581
|
}
|
|
4440
|
-
if (
|
|
4582
|
+
if (left.type !== AST_NODE_TYPES.MemberExpression) return;
|
|
4583
|
+
const object = Extract.unwrap(left.object);
|
|
4584
|
+
const property = Extract.getPropertyName(left.property);
|
|
4585
|
+
if (object.type === AST_NODE_TYPES.Identifier && property != null) addMemberBinding(object, property, bindingValueFrom(node.right), node.range[0]);
|
|
4441
4586
|
},
|
|
4442
4587
|
CallExpression(node) {
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4588
|
+
calls.push(node);
|
|
4589
|
+
},
|
|
4590
|
+
FunctionDeclaration(node) {
|
|
4591
|
+
if (node.id != null) addIdentifierBinding(node.id, {
|
|
4592
|
+
kind: "function",
|
|
4447
4593
|
node
|
|
4448
|
-
});
|
|
4449
|
-
else if (callee.type === AST_NODE_TYPES.MemberExpression) {
|
|
4450
|
-
const calleeObj = Extract.unwrap(callee.object);
|
|
4451
|
-
if (calleeObj.type === AST_NODE_TYPES.Identifier && calleeName != null) directCallSites.push({
|
|
4452
|
-
calleeName: `${calleeObj.name}.${calleeName}`,
|
|
4453
|
-
node
|
|
4454
|
-
});
|
|
4455
|
-
}
|
|
4456
|
-
if (calleeName === "useState") {
|
|
4457
|
-
const [firstArg] = node.arguments;
|
|
4458
|
-
if (firstArg != null) {
|
|
4459
|
-
const unwrappedFirstArg = Extract.unwrap(firstArg);
|
|
4460
|
-
if (isFunctionExpressionLike(unwrappedFirstArg)) stateInitializerFunctions.add(unwrappedFirstArg);
|
|
4461
|
-
}
|
|
4462
|
-
}
|
|
4463
|
-
if (calleeName != null && (calleeName.startsWith("use") || calleeName === "mergeRefs" || calleeName === "render")) return;
|
|
4464
|
-
for (const arg of node.arguments) {
|
|
4465
|
-
const unwrapped = Extract.unwrap(arg);
|
|
4466
|
-
if (unwrapped.type !== AST_NODE_TYPES.Identifier) continue;
|
|
4467
|
-
const resolvedName = resolveAlias(unwrapped.name, aliases);
|
|
4468
|
-
if (resolvedName === "ref" || resolvedName.endsWith("Ref") || jsxRefIdentifiers.has(resolvedName) || isInitializedFromRef$1(context, resolvedName, context.sourceCode.getScope(unwrapped))) refPassedToFunctions.push({
|
|
4469
|
-
callNode: node,
|
|
4470
|
-
node: unwrapped
|
|
4471
|
-
});
|
|
4472
|
-
}
|
|
4594
|
+
}, -1);
|
|
4473
4595
|
},
|
|
4474
4596
|
JSXAttribute(node) {
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
}
|
|
4481
|
-
}
|
|
4597
|
+
if (node.name.type !== AST_NODE_TYPES.JSXIdentifier || node.name.name !== "ref" || node.value?.type !== AST_NODE_TYPES.JSXExpressionContainer) return;
|
|
4598
|
+
const expression = Extract.unwrap(node.value.expression);
|
|
4599
|
+
if (expression.type !== AST_NODE_TYPES.Identifier) return;
|
|
4600
|
+
const variable = getVariable(expression);
|
|
4601
|
+
if (variable != null) jsxRefs.add(variable);
|
|
4482
4602
|
},
|
|
4483
4603
|
MemberExpression(node) {
|
|
4484
4604
|
if (Extract.getPropertyName(node.property) !== "current") return;
|
|
4485
|
-
refAccesses.push(
|
|
4486
|
-
isWrite: (() => {
|
|
4487
|
-
let parent = node.parent;
|
|
4488
|
-
while (Check.isTypeExpression(parent)) parent = parent.parent;
|
|
4489
|
-
return match(parent).with({ type: AST_NODE_TYPES.AssignmentExpression }, (p) => p.left === node || Extract.unwrap(p.left) === node).with({ type: AST_NODE_TYPES.UpdateExpression }, (p) => p.argument === node || Extract.unwrap(p.argument) === node).otherwise(() => false) || isNestedRefCurrentWrite(node);
|
|
4490
|
-
})(),
|
|
4491
|
-
node
|
|
4492
|
-
});
|
|
4605
|
+
refAccesses.push(getRefAccess(node));
|
|
4493
4606
|
},
|
|
4494
4607
|
"Program:exit"(program) {
|
|
4495
|
-
const
|
|
4496
|
-
const
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
const
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
case jsxRefIdentifiers.has(resolvedName):
|
|
4517
|
-
case isInitializedFromRef$1(context, resolvedName, context.sourceCode.getScope(node.object)):
|
|
4518
|
-
refName = resolvedName;
|
|
4519
|
-
break;
|
|
4520
|
-
default: continue;
|
|
4608
|
+
const boundaries = /* @__PURE__ */ new Set([...fc.api.getAllComponents(program).map((component) => component.node), ...hc.api.getAllHooks(program).map((hook) => hook.node)]);
|
|
4609
|
+
const isBoundary = (node) => {
|
|
4610
|
+
return Check.isFunction(node) && boundaries.has(node);
|
|
4611
|
+
};
|
|
4612
|
+
const reachedByBoundary = /* @__PURE__ */ new Map();
|
|
4613
|
+
for (const boundary of boundaries) reachedByBoundary.set(boundary, /* @__PURE__ */ new Set([boundary]));
|
|
4614
|
+
let changed = true;
|
|
4615
|
+
while (changed) {
|
|
4616
|
+
changed = false;
|
|
4617
|
+
for (const call of calls) {
|
|
4618
|
+
const boundary = Traverse.findParent(call, isBoundary);
|
|
4619
|
+
if (boundary == null) continue;
|
|
4620
|
+
const reached = reachedByBoundary.get(boundary);
|
|
4621
|
+
if (reached == null) continue;
|
|
4622
|
+
const host = Traverse.findParent(call, Check.isFunction) ?? boundary;
|
|
4623
|
+
if (!reached.has(host)) continue;
|
|
4624
|
+
const targets = [resolveCallable(call.callee, call.range[0])];
|
|
4625
|
+
for (const index of getSynchronousCallbackIndexes(call)) {
|
|
4626
|
+
const argument = call.arguments[index];
|
|
4627
|
+
if (argument == null || argument.type === AST_NODE_TYPES.SpreadElement) continue;
|
|
4628
|
+
targets.push(resolveCallable(argument, call.range[0]));
|
|
4521
4629
|
}
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
const boundary = Traverse.findParent(node, isCompOrHookFn);
|
|
4527
|
-
if (boundary == null) continue;
|
|
4528
|
-
if (!isReachedDuringRender(node, boundary, getReached(boundary), stateInitializerFunctions)) continue;
|
|
4529
|
-
let isLazyInit = refName != null && isInNullCheckTest(node);
|
|
4530
|
-
let matchedGuardIf = false;
|
|
4531
|
-
if (!isLazyInit && refName != null) {
|
|
4532
|
-
let current = node.parent;
|
|
4533
|
-
let prevChild = node;
|
|
4534
|
-
findLoop: while (true) {
|
|
4535
|
-
if (current.type === AST_NODE_TYPES.IfStatement) {
|
|
4536
|
-
const nullBranch = getRefCurrentNullCheckBranch(current.test, refName);
|
|
4537
|
-
const actualBranch = current.consequent === prevChild ? "consequent" : current.alternate === prevChild ? "alternate" : null;
|
|
4538
|
-
if (nullBranch != null && actualBranch != null) {
|
|
4539
|
-
matchedGuardIf = true;
|
|
4540
|
-
isLazyInit = actualBranch === nullBranch ? isWrite : !isWrite;
|
|
4541
|
-
}
|
|
4542
|
-
break;
|
|
4543
|
-
}
|
|
4544
|
-
switch (current.type) {
|
|
4545
|
-
case AST_NODE_TYPES.ExpressionStatement:
|
|
4546
|
-
case AST_NODE_TYPES.BlockStatement:
|
|
4547
|
-
case AST_NODE_TYPES.ReturnStatement:
|
|
4548
|
-
case AST_NODE_TYPES.JSXExpressionContainer:
|
|
4549
|
-
case AST_NODE_TYPES.JSXElement:
|
|
4550
|
-
case AST_NODE_TYPES.JSXOpeningElement:
|
|
4551
|
-
case AST_NODE_TYPES.JSXClosingElement:
|
|
4552
|
-
case AST_NODE_TYPES.AssignmentExpression:
|
|
4553
|
-
case AST_NODE_TYPES.VariableDeclaration:
|
|
4554
|
-
case AST_NODE_TYPES.VariableDeclarator:
|
|
4555
|
-
case AST_NODE_TYPES.MemberExpression:
|
|
4556
|
-
case AST_NODE_TYPES.ChainExpression:
|
|
4557
|
-
case AST_NODE_TYPES.CallExpression: break;
|
|
4558
|
-
default: break findLoop;
|
|
4559
|
-
}
|
|
4560
|
-
prevChild = current;
|
|
4561
|
-
current = current.parent;
|
|
4630
|
+
for (const target of targets) {
|
|
4631
|
+
if (target == null || reached.has(target) || Traverse.findParent(target, isBoundary) !== boundary) continue;
|
|
4632
|
+
reached.add(target);
|
|
4633
|
+
changed = true;
|
|
4562
4634
|
}
|
|
4563
4635
|
}
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4636
|
+
}
|
|
4637
|
+
function isReachedDuringRender(node, boundary) {
|
|
4638
|
+
const reached = reachedByBoundary.get(boundary);
|
|
4639
|
+
return reached != null && isReachedThroughFunctions(node, boundary, reached);
|
|
4640
|
+
}
|
|
4641
|
+
const initializedRefs = /* @__PURE__ */ new Map();
|
|
4642
|
+
for (const access of refAccesses.toSorted((a, b) => a.node.range[0] - b.node.range[0])) {
|
|
4643
|
+
const target = getRefTarget(access.node);
|
|
4644
|
+
if (target == null) continue;
|
|
4645
|
+
const boundary = Traverse.findParent(access.node, isBoundary);
|
|
4646
|
+
if (boundary == null || !isReachedDuringRender(access.node, boundary)) continue;
|
|
4647
|
+
let isLazyInitialization = false;
|
|
4648
|
+
if (target.identity != null) {
|
|
4649
|
+
const identity = target.identity;
|
|
4650
|
+
const getTargetNullBranch = (test) => getNullBranch(test, identity);
|
|
4651
|
+
if (isGuardTestAccess(access.node, getTargetNullBranch)) continue;
|
|
4652
|
+
const guard = getGuardDisposition(access.node, getTargetNullBranch);
|
|
4653
|
+
if (guard != null) {
|
|
4654
|
+
if (guard.inNullBranch && access.isInitializationWrite) isLazyInitialization = true;
|
|
4655
|
+
else if (!guard.inNullBranch && !access.isWrite) continue;
|
|
4656
|
+
} else if (access.isInitializationWrite && isAfterTerminatingNonNullGuard(access.node, getTargetNullBranch)) isLazyInitialization = true;
|
|
4579
4657
|
}
|
|
4580
|
-
if (
|
|
4581
|
-
const
|
|
4582
|
-
|
|
4583
|
-
if (
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
}
|
|
4590
|
-
seen.add(refName);
|
|
4658
|
+
if (isLazyInitialization && target.identity != null) {
|
|
4659
|
+
const initialized = initializedRefs.get(boundary) ?? /* @__PURE__ */ new Set();
|
|
4660
|
+
initializedRefs.set(boundary, initialized);
|
|
4661
|
+
if (initialized.has(target.identity)) context.report({
|
|
4662
|
+
messageId: "duplicateRefInit",
|
|
4663
|
+
node: access.node
|
|
4664
|
+
});
|
|
4665
|
+
else initialized.add(target.identity);
|
|
4666
|
+
continue;
|
|
4591
4667
|
}
|
|
4592
|
-
if (isLazyInit) continue;
|
|
4593
4668
|
context.report({
|
|
4594
|
-
messageId: isWrite ? "writeDuringRender" : "readDuringRender",
|
|
4595
|
-
node
|
|
4669
|
+
messageId: access.isWrite ? "writeDuringRender" : "readDuringRender",
|
|
4670
|
+
node: access.node
|
|
4596
4671
|
});
|
|
4597
4672
|
}
|
|
4598
|
-
for (const
|
|
4599
|
-
const boundary = Traverse.findParent(
|
|
4600
|
-
if (boundary == null) continue;
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4673
|
+
for (const call of calls) {
|
|
4674
|
+
const boundary = Traverse.findParent(call, isBoundary);
|
|
4675
|
+
if (boundary == null || !isReachedDuringRender(call, boundary)) continue;
|
|
4676
|
+
const callee = Extract.unwrap(call.callee);
|
|
4677
|
+
const calleeName = getCalleeName(call);
|
|
4678
|
+
const callArguments = call.arguments;
|
|
4679
|
+
if (core.isHookCall(call) || calleeName === "mergeRefs" || calleeName === "render" && callee.type === AST_NODE_TYPES.MemberExpression) continue;
|
|
4680
|
+
for (const argument of callArguments) {
|
|
4681
|
+
if (argument.type === AST_NODE_TYPES.SpreadElement) continue;
|
|
4682
|
+
const value = Extract.unwrap(argument);
|
|
4683
|
+
if (value.type !== AST_NODE_TYPES.Identifier) continue;
|
|
4684
|
+
const variable = getVariable(value);
|
|
4685
|
+
if (variable == null || resolveRef(variable, value.range[0]) == null) continue;
|
|
4686
|
+
context.report({
|
|
4687
|
+
messageId: "refPassedToFunction",
|
|
4688
|
+
node: value
|
|
4689
|
+
});
|
|
4690
|
+
}
|
|
4606
4691
|
}
|
|
4607
4692
|
},
|
|
4608
4693
|
VariableDeclarator(node) {
|
|
4609
|
-
if (node.init == null) return;
|
|
4610
|
-
|
|
4611
|
-
if (id.type !== AST_NODE_TYPES.Identifier) return;
|
|
4612
|
-
const init = Extract.unwrap(node.init);
|
|
4613
|
-
if (init.type === AST_NODE_TYPES.Identifier) {
|
|
4614
|
-
aliases.set(id.name, init.name);
|
|
4615
|
-
return;
|
|
4616
|
-
}
|
|
4617
|
-
if (isFunctionExpressionLike(init)) functionVarBindings.set(id.name, init);
|
|
4694
|
+
if (node.id.type !== AST_NODE_TYPES.Identifier || node.init == null) return;
|
|
4695
|
+
addIdentifierBinding(node.id, bindingValueFrom(node.init), node.range[0]);
|
|
4618
4696
|
}
|
|
4619
4697
|
});
|
|
4620
4698
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-react-x",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.14.0",
|
|
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/ast": "5.
|
|
49
|
-
"@eslint-react/
|
|
50
|
-
"@eslint-react/
|
|
51
|
-
"@eslint-react/
|
|
52
|
-
"@eslint-react/shared": "5.
|
|
53
|
-
"@eslint-react/
|
|
48
|
+
"@eslint-react/ast": "5.14.0",
|
|
49
|
+
"@eslint-react/eslint": "5.14.0",
|
|
50
|
+
"@eslint-react/jsx": "5.14.0",
|
|
51
|
+
"@eslint-react/core": "5.14.0",
|
|
52
|
+
"@eslint-react/shared": "5.14.0",
|
|
53
|
+
"@eslint-react/var": "5.14.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/react": "^19.2.17",
|
|
@@ -59,12 +59,12 @@
|
|
|
59
59
|
"eslint": "^10.6.0",
|
|
60
60
|
"react": "^19.2.7",
|
|
61
61
|
"react-dom": "^19.2.7",
|
|
62
|
-
"tsdown": "^0.22.
|
|
62
|
+
"tsdown": "^0.22.4",
|
|
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": "*",
|