eslint-plugin-react-x 5.10.4 → 5.11.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.js +201 -104
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { AST_NODE_TYPES } from "@typescript-eslint/types";
|
|
|
8
8
|
import { DefinitionType, ScopeType } from "@typescript-eslint/scope-manager";
|
|
9
9
|
import { findVariable, getStaticValue } from "@typescript-eslint/utils/ast-utils";
|
|
10
10
|
import { computeObjectType, isAssignmentTargetEqual, isInitializedFromReact, resolve, resolveEnclosingAssignmentTarget } from "@eslint-react/var";
|
|
11
|
-
import { P, match } from "ts-pattern";
|
|
11
|
+
import { P, isMatching, match } from "ts-pattern";
|
|
12
12
|
import { compare } from "compare-versions";
|
|
13
13
|
import { getConstrainedTypeAtLocation } from "@typescript-eslint/type-utils";
|
|
14
14
|
import { unionConstituents } from "ts-api-utils";
|
|
@@ -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.11.0";
|
|
148
148
|
|
|
149
149
|
//#endregion
|
|
150
150
|
//#region src/utils/create-rule.ts
|
|
@@ -2692,7 +2692,7 @@ function create$27(context) {
|
|
|
2692
2692
|
}, ({ left, right }) => {
|
|
2693
2693
|
if (left.type === AST_NODE_TYPES.UnaryExpression && left.operator === "!") return getReportDescriptor(right, visited);
|
|
2694
2694
|
const initialScope = context.sourceCode.getScope(left);
|
|
2695
|
-
if (Check.isIdentifier("NaN")
|
|
2695
|
+
if (Check.isIdentifier(left, "NaN") || getStaticValue(left, initialScope)?.value === "NaN") return {
|
|
2696
2696
|
data: { value: context.sourceCode.getText(left) },
|
|
2697
2697
|
messageId: "default",
|
|
2698
2698
|
node: left
|
|
@@ -2967,8 +2967,12 @@ function isProcessEnvNodeEnvCompare(context, node, operator, value) {
|
|
|
2967
2967
|
if (node == null) return false;
|
|
2968
2968
|
if (node.type !== AST_NODE_TYPES.BinaryExpression) return false;
|
|
2969
2969
|
if (node.operator !== operator) return false;
|
|
2970
|
-
|
|
2971
|
-
|
|
2970
|
+
const isStringLiteral = isMatching({
|
|
2971
|
+
type: AST_NODE_TYPES.Literal,
|
|
2972
|
+
value: P.string
|
|
2973
|
+
});
|
|
2974
|
+
if (isProcessEnvNodeEnv(context, node.left) && isStringLiteral(node.right)) return node.right.value === value;
|
|
2975
|
+
if (isStringLiteral(node.left) && isProcessEnvNodeEnv(context, node.right)) return node.left.value === value;
|
|
2972
2976
|
return false;
|
|
2973
2977
|
}
|
|
2974
2978
|
function isDevelopmentOnlyCheck(context, node) {
|
|
@@ -3428,7 +3432,7 @@ var no_unstable_context_value_default = createRule({
|
|
|
3428
3432
|
function create$13(context) {
|
|
3429
3433
|
const { compilationMode, version } = getSettingsFromContext(context);
|
|
3430
3434
|
if (compilationMode === "infer" || compilationMode === "all") return {};
|
|
3431
|
-
if (compilationMode === "annotation" && context.sourceCode.ast.body.some(Check.isDirective("use memo"))) return {};
|
|
3435
|
+
if (compilationMode === "annotation" && context.sourceCode.ast.body.some((stmt) => Check.isDirective(stmt, "use memo"))) return {};
|
|
3432
3436
|
const isReact18OrBelow = compare(version, "19.0.0", "<");
|
|
3433
3437
|
const { api, visitor } = core.getFunctionComponentCollector(context);
|
|
3434
3438
|
const constructions = /* @__PURE__ */ new WeakMap();
|
|
@@ -3527,7 +3531,7 @@ function extractIdentifier(node) {
|
|
|
3527
3531
|
function create$12(context, [options]) {
|
|
3528
3532
|
const { compilationMode } = getSettingsFromContext(context);
|
|
3529
3533
|
if (compilationMode === "infer" || compilationMode === "all") return {};
|
|
3530
|
-
if (compilationMode === "annotation" && context.sourceCode.ast.body.some(Check.isDirective("use memo"))) return {};
|
|
3534
|
+
if (compilationMode === "annotation" && context.sourceCode.ast.body.some((stmt) => Check.isDirective(stmt, "use memo"))) return {};
|
|
3531
3535
|
const { api, visitor } = core.getFunctionComponentCollector(context);
|
|
3532
3536
|
const declarators = /* @__PURE__ */ new WeakMap();
|
|
3533
3537
|
const { safeDefaultProps = [] } = options;
|
|
@@ -4379,6 +4383,7 @@ var refs_default = createRule({
|
|
|
4379
4383
|
type: "problem",
|
|
4380
4384
|
docs: { description: "Validates correct usage of refs by checking that 'ref.current' is not read or written during render." },
|
|
4381
4385
|
messages: {
|
|
4386
|
+
duplicateRefInit: "Ref is initialized more than once during render. Only a single 'if (ref.current == null)' initialization is allowed; move any additional initialization into an effect or event handler.",
|
|
4382
4387
|
readDuringRender: "Do not read 'ref.current' during render. Refs are not available during rendering and their values may be stale or inconsistent. Move this read into an effect or event handler.",
|
|
4383
4388
|
refPassedToFunction: "Passing a ref to a function may cause its value to be read during render. Pass 'ref.current' instead if the function only needs the value, or move the call into an effect.",
|
|
4384
4389
|
writeDuringRender: "Do not write to 'ref.current' during render. Refs should only be mutated in effects or event handlers. Move this write into an effect or event handler."
|
|
@@ -4397,6 +4402,50 @@ function resolveAlias(name, aliases) {
|
|
|
4397
4402
|
}
|
|
4398
4403
|
return name;
|
|
4399
4404
|
}
|
|
4405
|
+
function isFunctionExpressionLike(node) {
|
|
4406
|
+
return node.type === AST_NODE_TYPES.FunctionExpression || node.type === AST_NODE_TYPES.ArrowFunctionExpression;
|
|
4407
|
+
}
|
|
4408
|
+
/**
|
|
4409
|
+
* Phase 2 (part): compute the set of functions that are "reached" during render for a given
|
|
4410
|
+
* component/hook boundary. A function is reached if it is the boundary itself, or if it is
|
|
4411
|
+
* directly called (possibly through a simple variable alias, e.g. `const b = a; b();`) from
|
|
4412
|
+
* somewhere that is itself reached. This lets us detect ref mutations/reads that happen inside
|
|
4413
|
+
* a helper function which is invoked synchronously during render (see "Ref Mutation in Called
|
|
4414
|
+
* Function" in refs.spec.md), as opposed to functions that are merely defined and handed off
|
|
4415
|
+
* to be called later (e.g. event handlers, effect callbacks).
|
|
4416
|
+
*/
|
|
4417
|
+
function computeReachedFunctions(boundary, isCompOrHookFn, functionVarBindings, directCallSites, aliases) {
|
|
4418
|
+
const reached = /* @__PURE__ */ new Set([boundary]);
|
|
4419
|
+
const relevantCalls = directCallSites.filter((c) => Traverse.findParent(c.node, isCompOrHookFn) === boundary);
|
|
4420
|
+
let changed = true;
|
|
4421
|
+
for (let iterations = 0; changed && iterations < 50; iterations++) {
|
|
4422
|
+
changed = false;
|
|
4423
|
+
for (const { calleeName, node: callNode } of relevantCalls) {
|
|
4424
|
+
const hostFn = Traverse.findParent(callNode, Check.isFunction) ?? boundary;
|
|
4425
|
+
if (!reached.has(hostFn)) continue;
|
|
4426
|
+
const resolvedName = resolveAlias(calleeName, aliases);
|
|
4427
|
+
const target = functionVarBindings.get(resolvedName);
|
|
4428
|
+
if (target == null || reached.has(target)) continue;
|
|
4429
|
+
if (Traverse.findParent(target, isCompOrHookFn) !== boundary) continue;
|
|
4430
|
+
reached.add(target);
|
|
4431
|
+
changed = true;
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4434
|
+
return reached;
|
|
4435
|
+
}
|
|
4436
|
+
/**
|
|
4437
|
+
* Phase 2 (part): determine whether a ref access node is reached unconditionally during render,
|
|
4438
|
+
* i.e. every function boundary between the access and the component/hook `boundary` is itself
|
|
4439
|
+
* a function that gets invoked during render (see `computeReachedFunctions`).
|
|
4440
|
+
*/
|
|
4441
|
+
function isReachedDuringRender(node, boundary, reached) {
|
|
4442
|
+
let current = node.parent;
|
|
4443
|
+
while (current != null && current !== boundary) {
|
|
4444
|
+
if (Check.isFunction(current) && !reached.has(current)) return false;
|
|
4445
|
+
current = current.parent;
|
|
4446
|
+
}
|
|
4447
|
+
return true;
|
|
4448
|
+
}
|
|
4400
4449
|
function create$6(context) {
|
|
4401
4450
|
const hc = core.getHookCollector(context);
|
|
4402
4451
|
const fc = core.getFunctionComponentCollector(context);
|
|
@@ -4404,17 +4453,27 @@ function create$6(context) {
|
|
|
4404
4453
|
const jsxRefIdentifiers = /* @__PURE__ */ new Set();
|
|
4405
4454
|
const aliases = /* @__PURE__ */ new Map();
|
|
4406
4455
|
const refPassedToFunctions = [];
|
|
4456
|
+
const functionVarBindings = /* @__PURE__ */ new Map();
|
|
4457
|
+
const directCallSites = [];
|
|
4407
4458
|
return merge(hc.visitor, fc.visitor, {
|
|
4408
4459
|
AssignmentExpression(node) {
|
|
4409
4460
|
if (node.operator !== "=") return;
|
|
4410
4461
|
const left = Extract.unwrap(node.left);
|
|
4411
4462
|
const right = Extract.unwrap(node.right);
|
|
4412
|
-
if (left.type !== AST_NODE_TYPES.Identifier
|
|
4413
|
-
|
|
4463
|
+
if (left.type !== AST_NODE_TYPES.Identifier) return;
|
|
4464
|
+
if (right.type === AST_NODE_TYPES.Identifier) {
|
|
4465
|
+
aliases.set(left.name, right.name);
|
|
4466
|
+
return;
|
|
4467
|
+
}
|
|
4468
|
+
if (isFunctionExpressionLike(right)) functionVarBindings.set(left.name, right);
|
|
4414
4469
|
},
|
|
4415
4470
|
CallExpression(node) {
|
|
4416
4471
|
const callee = Extract.unwrap(node.callee);
|
|
4417
4472
|
const calleeName = callee.type === AST_NODE_TYPES.Identifier ? callee.name : callee.type === AST_NODE_TYPES.MemberExpression ? Extract.getPropertyName(callee.property) : null;
|
|
4473
|
+
if (callee.type === AST_NODE_TYPES.Identifier) directCallSites.push({
|
|
4474
|
+
calleeName: callee.name,
|
|
4475
|
+
node
|
|
4476
|
+
});
|
|
4418
4477
|
if (calleeName != null && (calleeName.startsWith("use") || calleeName === "mergeRefs")) return;
|
|
4419
4478
|
for (const arg of node.arguments) {
|
|
4420
4479
|
const unwrapped = Extract.unwrap(arg);
|
|
@@ -4451,26 +4510,46 @@ function create$6(context) {
|
|
|
4451
4510
|
const hooks = hc.api.getAllHooks(program);
|
|
4452
4511
|
const funcs = /* @__PURE__ */ new Set([...comps.map((c) => c.node), ...hooks.map((h) => h.node)]);
|
|
4453
4512
|
const isCompOrHookFn = (n) => Check.isFunction(n) && funcs.has(n);
|
|
4513
|
+
const reachedCache = /* @__PURE__ */ new Map();
|
|
4514
|
+
function getReached(boundary) {
|
|
4515
|
+
let reached = reachedCache.get(boundary);
|
|
4516
|
+
if (reached == null) {
|
|
4517
|
+
reached = computeReachedFunctions(boundary, isCompOrHookFn, functionVarBindings, directCallSites, aliases);
|
|
4518
|
+
reachedCache.set(boundary, reached);
|
|
4519
|
+
}
|
|
4520
|
+
return reached;
|
|
4521
|
+
}
|
|
4522
|
+
const lazyInitSeen = /* @__PURE__ */ new Map();
|
|
4454
4523
|
for (const { isWrite, node } of refAccesses) {
|
|
4455
4524
|
const obj = Extract.unwrap(node.object);
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4525
|
+
let refName = null;
|
|
4526
|
+
if (obj.type === AST_NODE_TYPES.Identifier) {
|
|
4527
|
+
const resolvedName = resolveAlias(obj.name, aliases);
|
|
4528
|
+
switch (true) {
|
|
4529
|
+
case resolvedName === "ref" || resolvedName.endsWith("Ref"):
|
|
4530
|
+
case jsxRefIdentifiers.has(resolvedName):
|
|
4531
|
+
case isInitializedFromRef$1(context, resolvedName, context.sourceCode.getScope(node.object)):
|
|
4532
|
+
refName = resolvedName;
|
|
4533
|
+
break;
|
|
4534
|
+
default: continue;
|
|
4535
|
+
}
|
|
4536
|
+
} else if (obj.type === AST_NODE_TYPES.MemberExpression) {
|
|
4537
|
+
const propName = Extract.getPropertyName(obj.property);
|
|
4538
|
+
if (propName == null || propName !== "ref" && !propName.endsWith("Ref")) continue;
|
|
4539
|
+
} else continue;
|
|
4464
4540
|
const boundary = Traverse.findParent(node, isCompOrHookFn);
|
|
4465
4541
|
if (boundary == null) continue;
|
|
4466
|
-
if (
|
|
4467
|
-
|
|
4468
|
-
let
|
|
4469
|
-
if (!isLazyInit) {
|
|
4542
|
+
if (!isReachedDuringRender(node, boundary, getReached(boundary))) continue;
|
|
4543
|
+
let isLazyInit = refName != null && isInNullCheckTest(node);
|
|
4544
|
+
let matchedGuardIf = false;
|
|
4545
|
+
if (!isLazyInit && refName != null) {
|
|
4470
4546
|
let current = node.parent;
|
|
4471
4547
|
findIf: for (;;) {
|
|
4472
4548
|
if (current.type === AST_NODE_TYPES.IfStatement) {
|
|
4473
|
-
if (isRefCurrentNullCheck(current.test, refName))
|
|
4549
|
+
if (isRefCurrentNullCheck(current.test, refName)) {
|
|
4550
|
+
isLazyInit = true;
|
|
4551
|
+
matchedGuardIf = true;
|
|
4552
|
+
}
|
|
4474
4553
|
break;
|
|
4475
4554
|
}
|
|
4476
4555
|
switch (current.type) {
|
|
@@ -4492,7 +4571,7 @@ function create$6(context) {
|
|
|
4492
4571
|
current = current.parent;
|
|
4493
4572
|
}
|
|
4494
4573
|
}
|
|
4495
|
-
if (!isLazyInit && isWrite) {
|
|
4574
|
+
if (!isLazyInit && isWrite && refName != null) {
|
|
4496
4575
|
let stmt = node;
|
|
4497
4576
|
while (stmt.parent != null && stmt.parent.type !== AST_NODE_TYPES.BlockStatement) stmt = stmt.parent;
|
|
4498
4577
|
if (stmt.parent?.type === AST_NODE_TYPES.BlockStatement) {
|
|
@@ -4508,6 +4587,18 @@ function create$6(context) {
|
|
|
4508
4587
|
}
|
|
4509
4588
|
}
|
|
4510
4589
|
}
|
|
4590
|
+
if (isLazyInit && matchedGuardIf && isWrite && refName != null) {
|
|
4591
|
+
const seen = lazyInitSeen.get(boundary) ?? /* @__PURE__ */ new Set();
|
|
4592
|
+
lazyInitSeen.set(boundary, seen);
|
|
4593
|
+
if (seen.has(refName)) {
|
|
4594
|
+
context.report({
|
|
4595
|
+
messageId: "duplicateRefInit",
|
|
4596
|
+
node
|
|
4597
|
+
});
|
|
4598
|
+
continue;
|
|
4599
|
+
}
|
|
4600
|
+
seen.add(refName);
|
|
4601
|
+
}
|
|
4511
4602
|
if (isLazyInit) continue;
|
|
4512
4603
|
context.report({
|
|
4513
4604
|
messageId: isWrite ? "writeDuringRender" : "readDuringRender",
|
|
@@ -4517,7 +4608,7 @@ function create$6(context) {
|
|
|
4517
4608
|
for (const { node } of refPassedToFunctions) {
|
|
4518
4609
|
const boundary = Traverse.findParent(node, isCompOrHookFn);
|
|
4519
4610
|
if (boundary == null) continue;
|
|
4520
|
-
if (
|
|
4611
|
+
if (!isReachedDuringRender(node, boundary, getReached(boundary))) continue;
|
|
4521
4612
|
context.report({
|
|
4522
4613
|
messageId: "refPassedToFunction",
|
|
4523
4614
|
node
|
|
@@ -4527,9 +4618,13 @@ function create$6(context) {
|
|
|
4527
4618
|
VariableDeclarator(node) {
|
|
4528
4619
|
if (node.init == null) return;
|
|
4529
4620
|
const id = node.id;
|
|
4621
|
+
if (id.type !== AST_NODE_TYPES.Identifier) return;
|
|
4530
4622
|
const init = Extract.unwrap(node.init);
|
|
4531
|
-
if (
|
|
4532
|
-
|
|
4623
|
+
if (init.type === AST_NODE_TYPES.Identifier) {
|
|
4624
|
+
aliases.set(id.name, init.name);
|
|
4625
|
+
return;
|
|
4626
|
+
}
|
|
4627
|
+
if (isFunctionExpressionLike(init)) functionVarBindings.set(id.name, init);
|
|
4533
4628
|
}
|
|
4534
4629
|
});
|
|
4535
4630
|
}
|
|
@@ -7193,34 +7288,49 @@ function create$4(context) {
|
|
|
7193
7288
|
|
|
7194
7289
|
//#endregion
|
|
7195
7290
|
//#region src/rules/static-components/lib.ts
|
|
7196
|
-
|
|
7291
|
+
/**
|
|
7292
|
+
* Expression kinds that always produce a brand-new value on every evaluation, and are
|
|
7293
|
+
* therefore considered "dynamically created" when used as a component's value.
|
|
7294
|
+
*/
|
|
7295
|
+
const DYNAMIC_EXPRESSION_TYPES = /* @__PURE__ */ new Set([
|
|
7296
|
+
AST_NODE_TYPES.ArrowFunctionExpression,
|
|
7297
|
+
AST_NODE_TYPES.CallExpression,
|
|
7298
|
+
AST_NODE_TYPES.ClassExpression,
|
|
7299
|
+
AST_NODE_TYPES.FunctionExpression,
|
|
7300
|
+
AST_NODE_TYPES.NewExpression
|
|
7301
|
+
]);
|
|
7302
|
+
function findVariableForIdentifier(context, identifier) {
|
|
7303
|
+
return findVariable(context.sourceCode.getScope(identifier), identifier.name);
|
|
7304
|
+
}
|
|
7305
|
+
/**
|
|
7306
|
+
* Resolves an expression down to the node that dynamically created its value, if any.
|
|
7307
|
+
* Recurses through identifier references and both branches of ternaries.
|
|
7308
|
+
*/
|
|
7309
|
+
function findDynamicCreationSite(context, node, isInsideRender, seen) {
|
|
7197
7310
|
const expr = Extract.unwrap(node);
|
|
7311
|
+
if (DYNAMIC_EXPRESSION_TYPES.has(expr.type)) return expr;
|
|
7198
7312
|
switch (expr.type) {
|
|
7199
|
-
case AST_NODE_TYPES.
|
|
7200
|
-
case AST_NODE_TYPES.ArrowFunctionExpression:
|
|
7201
|
-
case AST_NODE_TYPES.NewExpression:
|
|
7202
|
-
case AST_NODE_TYPES.CallExpression:
|
|
7203
|
-
case AST_NODE_TYPES.ClassExpression: return expr;
|
|
7204
|
-
case AST_NODE_TYPES.ConditionalExpression: {
|
|
7205
|
-
const consequent = resolveDynamicValue(context, expr.consequent, isInsideRender, seen);
|
|
7206
|
-
if (consequent != null) return consequent;
|
|
7207
|
-
return resolveDynamicValue(context, expr.alternate, isInsideRender, seen);
|
|
7208
|
-
}
|
|
7313
|
+
case AST_NODE_TYPES.ConditionalExpression: return findDynamicCreationSite(context, expr.consequent, isInsideRender, seen) ?? findDynamicCreationSite(context, expr.alternate, isInsideRender, seen);
|
|
7209
7314
|
case AST_NODE_TYPES.Identifier:
|
|
7210
7315
|
case AST_NODE_TYPES.JSXIdentifier: {
|
|
7211
|
-
const
|
|
7212
|
-
if (
|
|
7213
|
-
return getDynamicComponentSource(context,
|
|
7316
|
+
const variable = findVariableForIdentifier(context, expr);
|
|
7317
|
+
if (variable == null) return null;
|
|
7318
|
+
return getDynamicComponentSource(context, variable, isInsideRender, seen).creationNode;
|
|
7214
7319
|
}
|
|
7215
7320
|
default: return null;
|
|
7216
7321
|
}
|
|
7217
7322
|
}
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7323
|
+
/**
|
|
7324
|
+
* Looks for a reassignment of `variable` (e.g. `Component = createComponent();`) whose
|
|
7325
|
+
* right-hand side is dynamically created.
|
|
7326
|
+
*/
|
|
7327
|
+
function findReassignmentCreationSite(context, variable, isInsideRender, seen) {
|
|
7328
|
+
for (const ref of variable.references) {
|
|
7329
|
+
if (!ref.isWrite()) continue;
|
|
7330
|
+
const { identifier } = ref;
|
|
7331
|
+
if (identifier.parent.type !== AST_NODE_TYPES.AssignmentExpression || identifier.parent.left !== identifier) continue;
|
|
7332
|
+
const source = findDynamicCreationSite(context, identifier.parent.right, isInsideRender, seen);
|
|
7333
|
+
if (source != null) return source;
|
|
7224
7334
|
}
|
|
7225
7335
|
return null;
|
|
7226
7336
|
}
|
|
@@ -7233,34 +7343,21 @@ function getDynamicComponentSource(context, variable, isInsideRender, seen = /*
|
|
|
7233
7343
|
for (const def of variable.defs) {
|
|
7234
7344
|
const defNode = def.node;
|
|
7235
7345
|
if (!isInsideRender(defNode)) continue;
|
|
7236
|
-
if (defNode.type === AST_NODE_TYPES.FunctionDeclaration) return {
|
|
7346
|
+
if (defNode.type === AST_NODE_TYPES.FunctionDeclaration || defNode.type === AST_NODE_TYPES.ClassDeclaration) return {
|
|
7237
7347
|
creationNode: defNode,
|
|
7238
7348
|
isDynamic: true
|
|
7239
7349
|
};
|
|
7240
|
-
if (defNode.type
|
|
7241
|
-
|
|
7350
|
+
if (defNode.type !== AST_NODE_TYPES.VariableDeclarator) continue;
|
|
7351
|
+
const initSource = defNode.init == null ? null : findDynamicCreationSite(context, defNode.init, isInsideRender, seen);
|
|
7352
|
+
if (initSource != null) return {
|
|
7353
|
+
creationNode: initSource,
|
|
7354
|
+
isDynamic: true
|
|
7355
|
+
};
|
|
7356
|
+
const reassignmentSource = findReassignmentCreationSite(context, variable, isInsideRender, seen);
|
|
7357
|
+
if (reassignmentSource != null) return {
|
|
7358
|
+
creationNode: reassignmentSource,
|
|
7242
7359
|
isDynamic: true
|
|
7243
7360
|
};
|
|
7244
|
-
if (defNode.type === AST_NODE_TYPES.VariableDeclarator) {
|
|
7245
|
-
if (defNode.init != null) {
|
|
7246
|
-
const source = resolveDynamicValue(context, defNode.init, isInsideRender, seen);
|
|
7247
|
-
if (source != null) return {
|
|
7248
|
-
creationNode: source,
|
|
7249
|
-
isDynamic: true
|
|
7250
|
-
};
|
|
7251
|
-
}
|
|
7252
|
-
for (const ref of variable.references) {
|
|
7253
|
-
if (!ref.isWrite()) continue;
|
|
7254
|
-
const id = ref.identifier;
|
|
7255
|
-
if (id.parent.type === AST_NODE_TYPES.AssignmentExpression && id.parent.left === id) {
|
|
7256
|
-
const source = resolveDynamicValue(context, id.parent.right, isInsideRender, seen);
|
|
7257
|
-
if (source != null) return {
|
|
7258
|
-
creationNode: source,
|
|
7259
|
-
isDynamic: true
|
|
7260
|
-
};
|
|
7261
|
-
}
|
|
7262
|
-
}
|
|
7263
|
-
}
|
|
7264
7361
|
}
|
|
7265
7362
|
return {
|
|
7266
7363
|
creationNode: null,
|
|
@@ -7289,52 +7386,52 @@ function create$3(context) {
|
|
|
7289
7386
|
const hint = core.FunctionComponentDetectionHint.DoNotIncludeJsxWithNumberValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithBooleanValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithNullValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithStringValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithUndefinedValue | core.FunctionComponentDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx | core.FunctionComponentDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayPatternElement | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayExpressionElement | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback;
|
|
7290
7387
|
const fc = core.getFunctionComponentCollector(context, { hint });
|
|
7291
7388
|
const cc = core.getClassComponentCollector(context);
|
|
7292
|
-
const
|
|
7389
|
+
const candidates = [];
|
|
7293
7390
|
return merge(fc.visitor, cc.visitor, {
|
|
7294
7391
|
JSXOpeningElement(node) {
|
|
7295
7392
|
if (node.name.type !== AST_NODE_TYPES.JSXIdentifier) return;
|
|
7296
7393
|
const name = node.name.name;
|
|
7297
7394
|
if (!core.isFunctionComponentName(name)) return;
|
|
7298
|
-
|
|
7395
|
+
candidates.push({
|
|
7299
7396
|
name,
|
|
7300
|
-
node
|
|
7397
|
+
identifier: node.name
|
|
7301
7398
|
});
|
|
7302
7399
|
},
|
|
7303
7400
|
"Program:exit"(program) {
|
|
7304
|
-
const
|
|
7305
|
-
const
|
|
7306
|
-
function getEnclosingComponent(node) {
|
|
7307
|
-
return Traverse.findParent(node, (n) => {
|
|
7308
|
-
if (Check.isFunction(n)) return fComponents.some((c) => c.node === n);
|
|
7309
|
-
if (Check.isClass(n)) return cComponents.some((c) => c.node === n);
|
|
7310
|
-
return false;
|
|
7311
|
-
});
|
|
7312
|
-
}
|
|
7313
|
-
const isInsideRender = (node) => getEnclosingComponent(node) != null;
|
|
7314
|
-
for (const { name, node: jsxNode } of jsxCandidates) {
|
|
7315
|
-
const jsxName = jsxNode.name;
|
|
7316
|
-
const variable = findVariableForIdentifier(context, jsxName);
|
|
7317
|
-
if (variable == null || variable.defs.length === 0) continue;
|
|
7318
|
-
const def = variable.defs.at(0);
|
|
7319
|
-
if (def == null) continue;
|
|
7320
|
-
const defNode = def.node;
|
|
7321
|
-
if (getEnclosingComponent(defNode) == null) continue;
|
|
7322
|
-
const result = getDynamicComponentSource(context, variable, isInsideRender);
|
|
7323
|
-
if (!result.isDynamic) continue;
|
|
7324
|
-
context.report({
|
|
7325
|
-
data: { name },
|
|
7326
|
-
messageId: "default",
|
|
7327
|
-
node: jsxNode.name
|
|
7328
|
-
});
|
|
7329
|
-
if (result.creationNode != null) context.report({
|
|
7330
|
-
data: { name },
|
|
7331
|
-
messageId: "createdHere",
|
|
7332
|
-
node: result.creationNode
|
|
7333
|
-
});
|
|
7334
|
-
}
|
|
7401
|
+
const isInsideRender = createRenderBoundaryChecker(fc, cc, program);
|
|
7402
|
+
for (const candidate of candidates) reportIfCreatedDuringRender(context, candidate, isInsideRender);
|
|
7335
7403
|
}
|
|
7336
7404
|
});
|
|
7337
7405
|
}
|
|
7406
|
+
/**
|
|
7407
|
+
* Builds a predicate that tells whether a given node lies within the body of a
|
|
7408
|
+
* previously-collected function or class component, i.e. whether it is "created during render".
|
|
7409
|
+
*/
|
|
7410
|
+
function createRenderBoundaryChecker(fc, cc, program) {
|
|
7411
|
+
const componentNodes = new Set([...fc.api.getAllComponents(program), ...cc.api.getAllComponents(program)].map((component) => component.node));
|
|
7412
|
+
const getEnclosingComponent = (node) => Traverse.findParent(node, (n) => (Check.isFunction(n) || Check.isClass(n)) && componentNodes.has(n));
|
|
7413
|
+
return (node) => getEnclosingComponent(node) != null;
|
|
7414
|
+
}
|
|
7415
|
+
function reportIfCreatedDuringRender(context, candidate, isInsideRender) {
|
|
7416
|
+
const { name, identifier } = candidate;
|
|
7417
|
+
const variable = findVariableForIdentifier(context, identifier);
|
|
7418
|
+
if (variable == null) return;
|
|
7419
|
+
const def = variable.defs.at(0);
|
|
7420
|
+
if (def == null) return;
|
|
7421
|
+
if (!isInsideRender(def.node)) return;
|
|
7422
|
+
const { creationNode, isDynamic } = getDynamicComponentSource(context, variable, isInsideRender);
|
|
7423
|
+
if (!isDynamic) return;
|
|
7424
|
+
context.report({
|
|
7425
|
+
data: { name },
|
|
7426
|
+
messageId: "default",
|
|
7427
|
+
node: identifier
|
|
7428
|
+
});
|
|
7429
|
+
if (creationNode != null) context.report({
|
|
7430
|
+
data: { name },
|
|
7431
|
+
messageId: "createdHere",
|
|
7432
|
+
node: creationNode
|
|
7433
|
+
});
|
|
7434
|
+
}
|
|
7338
7435
|
|
|
7339
7436
|
//#endregion
|
|
7340
7437
|
//#region src/rules/unsupported-syntax/lib.ts
|
|
@@ -7343,7 +7440,7 @@ function isEvalCall(node) {
|
|
|
7343
7440
|
switch (callee.type) {
|
|
7344
7441
|
case AST_NODE_TYPES.Identifier: return callee.name === "eval";
|
|
7345
7442
|
case AST_NODE_TYPES.MemberExpression:
|
|
7346
|
-
if (!Check.isIdentifier(
|
|
7443
|
+
if (!Check.isIdentifier(Extract.unwrap(callee.object), "globalThis")) return false;
|
|
7347
7444
|
return Extract.getPropertyName(callee.property, (n) => callee.computed ? null : n.name) === "eval";
|
|
7348
7445
|
default: return false;
|
|
7349
7446
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-react-x",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.11.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/
|
|
49
|
-
"@eslint-react/
|
|
50
|
-
"@eslint-react/jsx": "5.
|
|
51
|
-
"@eslint-react/
|
|
52
|
-
"@eslint-react/
|
|
53
|
-
"@eslint-react/
|
|
48
|
+
"@eslint-react/core": "5.11.0",
|
|
49
|
+
"@eslint-react/ast": "5.11.0",
|
|
50
|
+
"@eslint-react/jsx": "5.11.0",
|
|
51
|
+
"@eslint-react/shared": "5.11.0",
|
|
52
|
+
"@eslint-react/eslint": "5.11.0",
|
|
53
|
+
"@eslint-react/var": "5.11.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/react": "^19.2.17",
|