oxlint-plugin-react-doctor 0.7.7-dev.517f87a → 0.7.7-dev.6e5c240
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 +1706 -294
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10558,7 +10558,7 @@ const getAssignedName = (node) => {
|
|
|
10558
10558
|
}
|
|
10559
10559
|
return null;
|
|
10560
10560
|
};
|
|
10561
|
-
const isModuleExportsAssignment = (node) => {
|
|
10561
|
+
const isModuleExportsAssignment$1 = (node) => {
|
|
10562
10562
|
const parent = node.parent;
|
|
10563
10563
|
if (!parent || !isNodeOfType(parent, "AssignmentExpression")) return false;
|
|
10564
10564
|
const left = parent.left;
|
|
@@ -10717,7 +10717,7 @@ const displayName = defineRule({
|
|
|
10717
10717
|
}
|
|
10718
10718
|
const assignedName = getAssignedName(node);
|
|
10719
10719
|
if (assignedName && isReactComponentName(assignedName) && ignoreNamed) return;
|
|
10720
|
-
if (isModuleExportsAssignment(node) && !node.id) {
|
|
10720
|
+
if (isModuleExportsAssignment$1(node) && !node.id) {
|
|
10721
10721
|
reportAt(node);
|
|
10722
10722
|
return;
|
|
10723
10723
|
}
|
|
@@ -10739,7 +10739,7 @@ const displayName = defineRule({
|
|
|
10739
10739
|
reportAt(node);
|
|
10740
10740
|
return;
|
|
10741
10741
|
}
|
|
10742
|
-
if (isModuleExportsAssignment(node)) {
|
|
10742
|
+
if (isModuleExportsAssignment$1(node)) {
|
|
10743
10743
|
reportAt(node);
|
|
10744
10744
|
return;
|
|
10745
10745
|
}
|
|
@@ -11673,6 +11673,25 @@ const getFunctionBindingName$1 = (functionNode) => getFunctionBindingIdentifier(
|
|
|
11673
11673
|
//#region src/plugin/utils/is-event-handler-attribute.ts
|
|
11674
11674
|
const isEventHandlerAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && /^on[A-Z]/.test(node.name.name);
|
|
11675
11675
|
//#endregion
|
|
11676
|
+
//#region src/plugin/utils/is-ast-descendant.ts
|
|
11677
|
+
/**
|
|
11678
|
+
* True when `inner` is `outer` itself or any descendant in the AST
|
|
11679
|
+
* `parent` chain. Walks `inner.parent` upward and stops at either a
|
|
11680
|
+
* match (`true`) or the chain's root (`false`).
|
|
11681
|
+
*
|
|
11682
|
+
* Was duplicated byte-identical in:
|
|
11683
|
+
* - exhaustive-deps-symbol-stability.ts
|
|
11684
|
+
* - semantic/closure-captures.ts
|
|
11685
|
+
*/
|
|
11686
|
+
const isAstDescendant = (inner, outer) => {
|
|
11687
|
+
let current = inner;
|
|
11688
|
+
while (current) {
|
|
11689
|
+
if (current === outer) return true;
|
|
11690
|
+
current = current.parent ?? null;
|
|
11691
|
+
}
|
|
11692
|
+
return false;
|
|
11693
|
+
};
|
|
11694
|
+
//#endregion
|
|
11676
11695
|
//#region src/plugin/utils/react-ref-origin.ts
|
|
11677
11696
|
const resolveReactRefSymbol = (memberExpression, scopes) => {
|
|
11678
11697
|
const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
|
|
@@ -12322,26 +12341,39 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
|
|
|
12322
12341
|
});
|
|
12323
12342
|
return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
|
|
12324
12343
|
};
|
|
12344
|
+
const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => {
|
|
12345
|
+
if (usage.handleKey === null) return null;
|
|
12346
|
+
const doesTestRequireLiveHandle = (test) => {
|
|
12347
|
+
if (resolveExpressionKey(test, context) === usage.handleKey) return true;
|
|
12348
|
+
const unwrappedTest = stripParenExpression(test);
|
|
12349
|
+
if (!isNodeOfType(unwrappedTest, "BinaryExpression") || unwrappedTest.operator !== "!=" && unwrappedTest.operator !== "!==") return false;
|
|
12350
|
+
const isNullishOperand = (operand) => {
|
|
12351
|
+
const unwrappedOperand = stripParenExpression(operand);
|
|
12352
|
+
return isNodeOfType(unwrappedOperand, "Literal") && unwrappedOperand.value === null || isNodeOfType(unwrappedOperand, "Identifier") && unwrappedOperand.name === "undefined" && context.scopes.isGlobalReference(unwrappedOperand);
|
|
12353
|
+
};
|
|
12354
|
+
return resolveExpressionKey(unwrappedTest.left, context) === usage.handleKey && isNullishOperand(unwrappedTest.right) || resolveExpressionKey(unwrappedTest.right, context) === usage.handleKey && isNullishOperand(unwrappedTest.left);
|
|
12355
|
+
};
|
|
12356
|
+
let ancestor = releaseCall.parent;
|
|
12357
|
+
while (ancestor && ancestor !== owner) {
|
|
12358
|
+
if (isNodeOfType(ancestor, "IfStatement")) {
|
|
12359
|
+
if (ancestor.alternate !== null || !doesTestRequireLiveHandle(ancestor.test) || !doMatchingNodesCoverEveryPathAfterUsage(ancestor.consequent, [releaseCall], context)) return null;
|
|
12360
|
+
return ancestor;
|
|
12361
|
+
}
|
|
12362
|
+
ancestor = ancestor.parent;
|
|
12363
|
+
}
|
|
12364
|
+
return null;
|
|
12365
|
+
};
|
|
12325
12366
|
const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
|
|
12326
12367
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
|
|
12327
12368
|
const functionCfg = context.cfg.cfgFor(callback);
|
|
12328
12369
|
const usageBlock = functionCfg?.blockOf(usage.node);
|
|
12329
12370
|
const usageStart = getRangeStart(usage.node);
|
|
12330
12371
|
if (!functionCfg || !usageBlock || usageStart === null) return false;
|
|
12331
|
-
const findHandleGuard = (releaseCall) => {
|
|
12332
|
-
if (usage.handleKey === null) return null;
|
|
12333
|
-
let ancestor = releaseCall.parent;
|
|
12334
|
-
while (ancestor && ancestor !== callback.body) {
|
|
12335
|
-
if (isNodeOfType(ancestor, "IfStatement")) return ancestor.alternate === null && resolveExpressionKey(ancestor.test, context) === usage.handleKey && getRangeStart(ancestor) !== null && (getRangeStart(ancestor) ?? usageStart) < usageStart ? ancestor : null;
|
|
12336
|
-
ancestor = ancestor.parent;
|
|
12337
|
-
}
|
|
12338
|
-
return null;
|
|
12339
|
-
};
|
|
12340
12372
|
const matchingReleaseAnchors = [];
|
|
12341
12373
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
12342
12374
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
12343
12375
|
const releaseStart = getRangeStart(child);
|
|
12344
|
-
const handleGuard =
|
|
12376
|
+
const handleGuard = findDirectHandleGuardForRelease(child, callback, usage, context);
|
|
12345
12377
|
if (releaseStart === null || releaseStart >= usageStart || functionCfg.blockOf(child) !== usageBlock && !handleGuard) return;
|
|
12346
12378
|
if (doesReleaseCallMatchUsage(child, usage, context)) {
|
|
12347
12379
|
matchingReleaseAnchors.push(handleGuard ?? child);
|
|
@@ -12371,12 +12403,12 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
|
12371
12403
|
return didFindUnmountCleanup;
|
|
12372
12404
|
};
|
|
12373
12405
|
const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
|
|
12374
|
-
const collectBlockingBooleanStates = (expression, blockedExpressionValue, context) => {
|
|
12406
|
+
const collectBlockingBooleanStates = (expression, blockedExpressionValue, guardNode, context) => {
|
|
12375
12407
|
const unwrappedExpression = stripParenExpression(expression);
|
|
12376
|
-
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, context);
|
|
12408
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, guardNode, context);
|
|
12377
12409
|
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
|
|
12378
12410
|
if (!(unwrappedExpression.operator === "||" && blockedExpressionValue || unwrappedExpression.operator === "&&" && !blockedExpressionValue)) return [];
|
|
12379
|
-
return [...collectBlockingBooleanStates(unwrappedExpression.left, blockedExpressionValue, context), ...collectBlockingBooleanStates(unwrappedExpression.right, blockedExpressionValue, context)];
|
|
12411
|
+
return [...collectBlockingBooleanStates(unwrappedExpression.left, blockedExpressionValue, guardNode, context), ...collectBlockingBooleanStates(unwrappedExpression.right, blockedExpressionValue, guardNode, context)];
|
|
12380
12412
|
}
|
|
12381
12413
|
if (isNodeOfType(unwrappedExpression, "BinaryExpression") && [
|
|
12382
12414
|
"===",
|
|
@@ -12387,35 +12419,59 @@ const collectBlockingBooleanStates = (expression, blockedExpressionValue, contex
|
|
|
12387
12419
|
const leftValue = readStaticBoolean(unwrappedExpression.left);
|
|
12388
12420
|
const rightValue = readStaticBoolean(unwrappedExpression.right);
|
|
12389
12421
|
const booleanValue = leftValue ?? rightValue;
|
|
12390
|
-
const
|
|
12422
|
+
const comparedExpression = leftValue === null ? unwrappedExpression.left : unwrappedExpression.right;
|
|
12423
|
+
const comparedKey = resolveExpressionKey(comparedExpression, context);
|
|
12391
12424
|
if (booleanValue === null || comparedKey === null) return [];
|
|
12425
|
+
const isEquality = unwrappedExpression.operator === "===" || unwrappedExpression.operator === "==";
|
|
12392
12426
|
return [{
|
|
12427
|
+
bindingIdentifier: isNodeOfType(comparedExpression, "Identifier") ? context.scopes.symbolFor(comparedExpression)?.bindingIdentifier ?? null : null,
|
|
12428
|
+
guardNode,
|
|
12393
12429
|
key: comparedKey,
|
|
12394
|
-
value:
|
|
12430
|
+
value: isEquality === blockedExpressionValue ? booleanValue : !booleanValue
|
|
12395
12431
|
}];
|
|
12396
12432
|
}
|
|
12397
12433
|
const expressionKey = resolveExpressionKey(unwrappedExpression, context);
|
|
12398
12434
|
return expressionKey === null ? [] : [{
|
|
12435
|
+
bindingIdentifier: isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression)?.bindingIdentifier ?? null : null,
|
|
12436
|
+
guardNode,
|
|
12399
12437
|
key: expressionKey,
|
|
12400
12438
|
value: blockedExpressionValue
|
|
12401
12439
|
}];
|
|
12402
12440
|
};
|
|
12403
|
-
const
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
|
|
12441
|
+
const canNodeReachLaterNodeWithinFunction = (sourceNode, targetNode, owner, context) => {
|
|
12442
|
+
const functionCfg = context.cfg.cfgFor(owner);
|
|
12443
|
+
const sourceBlock = functionCfg?.blockOf(sourceNode);
|
|
12444
|
+
const targetBlock = functionCfg?.blockOf(targetNode);
|
|
12445
|
+
const sourceStart = getRangeStart(sourceNode);
|
|
12446
|
+
const targetStart = getRangeStart(targetNode);
|
|
12447
|
+
if (!functionCfg || !sourceBlock || !targetBlock || sourceStart === null || targetStart === null) return true;
|
|
12448
|
+
if (!isNodeReachableWithinFunction(sourceNode, context)) return false;
|
|
12449
|
+
if (sourceBlock === targetBlock) return sourceStart < targetStart;
|
|
12450
|
+
const visitedBlocks = new Set([sourceBlock]);
|
|
12451
|
+
const pendingBlocks = [sourceBlock];
|
|
12452
|
+
while (pendingBlocks.length > 0) {
|
|
12453
|
+
const currentBlock = pendingBlocks.pop();
|
|
12454
|
+
if (!currentBlock) break;
|
|
12455
|
+
for (const edge of currentBlock.successors) {
|
|
12456
|
+
if (edge.to === targetBlock) return true;
|
|
12457
|
+
if (visitedBlocks.has(edge.to)) continue;
|
|
12458
|
+
visitedBlocks.add(edge.to);
|
|
12459
|
+
pendingBlocks.push(edge.to);
|
|
12460
|
+
}
|
|
12461
|
+
}
|
|
12462
|
+
return false;
|
|
12407
12463
|
};
|
|
12408
12464
|
const collectDeferredUsageGuardStates = (callback, usageNode, context) => {
|
|
12409
12465
|
if (!isFunctionLike$1(callback) || callback.async) return [];
|
|
12410
12466
|
const guardStates = [];
|
|
12411
12467
|
walkAst(callback.body, (child) => {
|
|
12412
12468
|
if (child !== callback.body && isFunctionLike$1(child)) return false;
|
|
12413
|
-
if (isNodeOfType(child, "IfStatement") &&
|
|
12469
|
+
if (isNodeOfType(child, "IfStatement") && !child.alternate && !canNodeReachLaterNodeWithinFunction(child.consequent, usageNode, callback, context) && doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], callback, context)) guardStates.push(...collectBlockingBooleanStates(child.test, true, child, context));
|
|
12414
12470
|
});
|
|
12415
12471
|
let descendant = usageNode;
|
|
12416
12472
|
let ancestor = descendant.parent;
|
|
12417
12473
|
while (ancestor && ancestor !== callback) {
|
|
12418
|
-
if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant) guardStates.push(...collectBlockingBooleanStates(ancestor.test, false, context));
|
|
12474
|
+
if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant) guardStates.push(...collectBlockingBooleanStates(ancestor.test, false, ancestor, context));
|
|
12419
12475
|
descendant = ancestor;
|
|
12420
12476
|
ancestor = ancestor.parent;
|
|
12421
12477
|
}
|
|
@@ -12424,7 +12480,7 @@ const collectDeferredUsageGuardStates = (callback, usageNode, context) => {
|
|
|
12424
12480
|
const cleanupReturnInvalidatesGuard = (cleanupReturn, guardState, context) => {
|
|
12425
12481
|
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
|
|
12426
12482
|
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
12427
|
-
if (!cleanupFunction || !isFunctionLike$1(cleanupFunction) || cleanupFunction.async) return false;
|
|
12483
|
+
if (!cleanupFunction || !isFunctionLike$1(cleanupFunction) || cleanupFunction.async || cleanupFunction.generator) return false;
|
|
12428
12484
|
let didInvalidateGuard = false;
|
|
12429
12485
|
walkAst(cleanupFunction.body, (child) => {
|
|
12430
12486
|
if (didInvalidateGuard) return false;
|
|
@@ -12453,11 +12509,110 @@ const deferredUsageWritesGuardBeforeUsage = (callback, usageNode, guardState, co
|
|
|
12453
12509
|
});
|
|
12454
12510
|
return didWriteGuard;
|
|
12455
12511
|
};
|
|
12512
|
+
const canInterruptionReachUsageThroughCatch = (interruptionNode, usageNode, owner, context) => {
|
|
12513
|
+
let descendant = interruptionNode;
|
|
12514
|
+
let ancestor = descendant.parent;
|
|
12515
|
+
while (ancestor && ancestor !== owner) {
|
|
12516
|
+
if (isNodeOfType(ancestor, "TryStatement") && ancestor.block === descendant && ancestor.handler && canNodeReachLaterNodeWithinFunction(ancestor.handler.body, usageNode, owner, context)) return true;
|
|
12517
|
+
descendant = ancestor;
|
|
12518
|
+
ancestor = ancestor.parent;
|
|
12519
|
+
}
|
|
12520
|
+
return false;
|
|
12521
|
+
};
|
|
12522
|
+
const isEffectLocalLifecycleGuard = (callback, guardState, cleanupFunctions, context) => {
|
|
12523
|
+
if (!guardState.bindingIdentifier) return false;
|
|
12524
|
+
const guardSymbol = context.scopes.symbolFor(guardState.bindingIdentifier);
|
|
12525
|
+
if (!guardSymbol || guardSymbol.kind !== "let" && guardSymbol.kind !== "var" || !isNodeOfType(guardSymbol.declarationNode, "VariableDeclarator") || findEnclosingFunction$1(guardSymbol.declarationNode) !== callback) return false;
|
|
12526
|
+
return guardSymbol.references.every((reference) => {
|
|
12527
|
+
if (!isWithinAssignmentTarget(reference.identifier)) return true;
|
|
12528
|
+
const assignmentTarget = findTransparentExpressionRoot(reference.identifier);
|
|
12529
|
+
const assignment = assignmentTarget.parent;
|
|
12530
|
+
return isNodeOfType(assignment, "AssignmentExpression") && assignment.operator === "=" && assignment.left === assignmentTarget && readStaticBoolean(assignment.right) === guardState.value && cleanupFunctions.includes(findEnclosingFunction$1(assignment) ?? assignment);
|
|
12531
|
+
});
|
|
12532
|
+
};
|
|
12533
|
+
const hasPotentialInterruptionAfterGuard = (callback, guardState, usageNode, context) => {
|
|
12534
|
+
if (!isFunctionLike$1(callback)) return true;
|
|
12535
|
+
const guardStart = getRangeStart(guardState.guardNode);
|
|
12536
|
+
const usageStart = getRangeStart(usageNode);
|
|
12537
|
+
if (guardStart === null || usageStart === null) return true;
|
|
12538
|
+
let hasPotentialInterruption = false;
|
|
12539
|
+
walkAst(callback.body, (child) => {
|
|
12540
|
+
if (hasPotentialInterruption) return false;
|
|
12541
|
+
if (child !== callback.body && isFunctionLike$1(child)) return false;
|
|
12542
|
+
const childStart = getRangeStart(child);
|
|
12543
|
+
if (childStart === null || childStart <= guardStart || childStart >= usageStart) return;
|
|
12544
|
+
if (isNodeOfType(child, "CallExpression") || isNodeOfType(child, "AwaitExpression") || isNodeOfType(child, "YieldExpression")) {
|
|
12545
|
+
if (canNodeReachLaterNodeWithinFunction(child, usageNode, callback, context) || canInterruptionReachUsageThroughCatch(child, usageNode, callback, context)) {
|
|
12546
|
+
hasPotentialInterruption = true;
|
|
12547
|
+
return false;
|
|
12548
|
+
}
|
|
12549
|
+
}
|
|
12550
|
+
});
|
|
12551
|
+
return hasPotentialInterruption;
|
|
12552
|
+
};
|
|
12456
12553
|
const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
|
|
12457
12554
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
12458
12555
|
const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
|
|
12459
|
-
if (usage.handleKey === null || !usageFunction || usageFunction === callback || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
|
|
12460
|
-
|
|
12556
|
+
if (usage.kind !== "timer" || usage.handleKey === null || !usageFunction || !isFunctionLike$1(usageFunction) || usageFunction === callback || usageFunction.async || usageFunction.generator || !isNodeOfType(usage.node, "CallExpression") || !isNodeOfType(usage.node.callee, "Identifier") || !context.scopes.isGlobalReference(usage.node.callee) || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
|
|
12557
|
+
const usageExpression = findTransparentExpressionRoot(usage.node);
|
|
12558
|
+
const usageAssignment = usageExpression.parent;
|
|
12559
|
+
if (!isNodeOfType(usageAssignment, "AssignmentExpression") || usageAssignment.operator !== "=" || usageAssignment.right !== usageExpression || !isNodeOfType(usageAssignment.left, "Identifier")) return false;
|
|
12560
|
+
const handleSymbol = context.scopes.symbolFor(usageAssignment.left);
|
|
12561
|
+
if (!handleSymbol || handleSymbol.kind !== "let" && handleSymbol.kind !== "var" || !isNodeOfType(handleSymbol.declarationNode, "VariableDeclarator") || findEnclosingFunction$1(handleSymbol.declarationNode) !== callback) return false;
|
|
12562
|
+
const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
|
|
12563
|
+
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
|
|
12564
|
+
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
12565
|
+
return cleanupFunction && isFunctionLike$1(cleanupFunction) ? [cleanupFunction] : [];
|
|
12566
|
+
});
|
|
12567
|
+
if (cleanupFunctions.length !== cleanupReturns.length) return false;
|
|
12568
|
+
const globalReleaseProofsByCleanup = /* @__PURE__ */ new Map();
|
|
12569
|
+
for (const cleanupFunction of cleanupFunctions) {
|
|
12570
|
+
if (!isFunctionLike$1(cleanupFunction)) return false;
|
|
12571
|
+
const globalReleaseProofs = [];
|
|
12572
|
+
walkAst(cleanupFunction.body, (child) => {
|
|
12573
|
+
if (child !== cleanupFunction.body && isFunctionLike$1(child)) return false;
|
|
12574
|
+
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && context.scopes.isGlobalReference(child.callee) && doesReleaseCallMatchUsage(child, usage, context)) {
|
|
12575
|
+
const handleGuard = findDirectHandleGuardForRelease(child, cleanupFunction, usage, context);
|
|
12576
|
+
globalReleaseProofs.push({
|
|
12577
|
+
anchor: handleGuard ?? child,
|
|
12578
|
+
call: child,
|
|
12579
|
+
handleGuard
|
|
12580
|
+
});
|
|
12581
|
+
}
|
|
12582
|
+
});
|
|
12583
|
+
if (!doMatchingNodesCoverEveryPathFromFunctionEntry(cleanupFunction, globalReleaseProofs.map((releaseProof) => releaseProof.anchor), context)) return false;
|
|
12584
|
+
globalReleaseProofsByCleanup.set(cleanupFunction, globalReleaseProofs);
|
|
12585
|
+
}
|
|
12586
|
+
const handleAssignments = handleSymbol.references.filter((reference) => isWithinAssignmentTarget(reference.identifier));
|
|
12587
|
+
const hasUsageAssignment = handleAssignments.some((handleAssignment) => findTransparentExpressionRoot(handleAssignment.identifier).parent === usageAssignment);
|
|
12588
|
+
const hasUnsafeHandleAssignment = handleAssignments.some((handleAssignment) => {
|
|
12589
|
+
const assignmentTarget = findTransparentExpressionRoot(handleAssignment.identifier);
|
|
12590
|
+
const assignment = assignmentTarget.parent;
|
|
12591
|
+
if (assignment === usageAssignment) return false;
|
|
12592
|
+
if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== assignmentTarget) return true;
|
|
12593
|
+
const assignedValue = stripParenExpression(assignment.right);
|
|
12594
|
+
if (!(isNodeOfType(assignedValue, "Literal") && assignedValue.value === null || isNodeOfType(assignedValue, "Identifier") && assignedValue.name === "undefined" && context.scopes.isGlobalReference(assignedValue))) return true;
|
|
12595
|
+
const cleanupFunction = findEnclosingFunction$1(assignment);
|
|
12596
|
+
const globalReleaseProofs = cleanupFunction ? globalReleaseProofsByCleanup.get(cleanupFunction) : void 0;
|
|
12597
|
+
return !(cleanupFunction && globalReleaseProofs && doMatchingNodesCoverEveryPathBeforeUsage(assignment, globalReleaseProofs.map((releaseProof) => releaseProof.handleGuard && isAstDescendant(assignment, releaseProof.handleGuard.consequent) ? releaseProof.call : releaseProof.anchor), cleanupFunction, context));
|
|
12598
|
+
});
|
|
12599
|
+
if (!hasUsageAssignment || hasUnsafeHandleAssignment) return false;
|
|
12600
|
+
let usageAncestor = usage.node.parent;
|
|
12601
|
+
while (usageAncestor && usageAncestor !== usageFunction) {
|
|
12602
|
+
if (isNodeOfType(usageAncestor, "ForStatement") || isNodeOfType(usageAncestor, "ForInStatement") || isNodeOfType(usageAncestor, "ForOfStatement") || isNodeOfType(usageAncestor, "WhileStatement") || isNodeOfType(usageAncestor, "DoWhileStatement")) return false;
|
|
12603
|
+
usageAncestor = usageAncestor.parent;
|
|
12604
|
+
}
|
|
12605
|
+
let hasPotentialInterruption = false;
|
|
12606
|
+
for (const argument of usage.node.arguments ?? []) walkAst(argument, (argumentChild) => {
|
|
12607
|
+
if (hasPotentialInterruption) return false;
|
|
12608
|
+
if (isFunctionLike$1(argumentChild)) return false;
|
|
12609
|
+
if (isNodeOfType(argumentChild, "CallExpression") || isNodeOfType(argumentChild, "AwaitExpression") || isNodeOfType(argumentChild, "YieldExpression")) {
|
|
12610
|
+
hasPotentialInterruption = true;
|
|
12611
|
+
return false;
|
|
12612
|
+
}
|
|
12613
|
+
});
|
|
12614
|
+
if (hasPotentialInterruption) return false;
|
|
12615
|
+
return collectDeferredUsageGuardStates(usageFunction, usage.node, context).some((guardState) => isEffectLocalLifecycleGuard(callback, guardState, cleanupFunctions, context) && !hasPotentialInterruptionAfterGuard(usageFunction, guardState, usage.node, context) && !deferredUsageWritesGuardBeforeUsage(usageFunction, usage.node, guardState, context) && cleanupReturns.every((cleanupReturn) => cleanupReturnInvalidatesGuard(cleanupReturn, guardState, context)));
|
|
12461
12616
|
};
|
|
12462
12617
|
const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
12463
12618
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
@@ -13023,7 +13178,7 @@ const effectNeedsCleanup = defineRule({
|
|
|
13023
13178
|
const hookName = getCalleeName$2(node) ?? "effect";
|
|
13024
13179
|
context.report({
|
|
13025
13180
|
node,
|
|
13026
|
-
message: `\`${firstUsage.resourceName}\` creates a ${resourceNoun} in ${hookName} without
|
|
13181
|
+
message: `\`${firstUsage.resourceName}\` creates a ${resourceNoun} in ${hookName} without guaranteed cleanup. Return a cleanup function that owns every allocation so it does not leak after unmount.`
|
|
13027
13182
|
});
|
|
13028
13183
|
},
|
|
13029
13184
|
FunctionDeclaration(node) {
|
|
@@ -14368,6 +14523,22 @@ const isReactHocCallbackArgument = (functionNode) => {
|
|
|
14368
14523
|
return calleeName !== null && REACT_HOC_NAMES.has(calleeName);
|
|
14369
14524
|
};
|
|
14370
14525
|
//#endregion
|
|
14526
|
+
//#region src/plugin/utils/is-outside-all-functions.ts
|
|
14527
|
+
const FUNCTION_SCOPE_KINDS = new Set([
|
|
14528
|
+
"function",
|
|
14529
|
+
"arrow-function",
|
|
14530
|
+
"method"
|
|
14531
|
+
]);
|
|
14532
|
+
const isOutsideAllFunctions = (symbol) => {
|
|
14533
|
+
let scope = symbol.scope;
|
|
14534
|
+
while (scope) {
|
|
14535
|
+
if (FUNCTION_SCOPE_KINDS.has(scope.kind)) return false;
|
|
14536
|
+
if (scope.kind === "module") return true;
|
|
14537
|
+
scope = scope.parent;
|
|
14538
|
+
}
|
|
14539
|
+
return true;
|
|
14540
|
+
};
|
|
14541
|
+
//#endregion
|
|
14371
14542
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-low-level.ts
|
|
14372
14543
|
/**
|
|
14373
14544
|
* Lowest-level helpers consumed by both the main `exhaustive-deps`
|
|
@@ -14420,25 +14591,6 @@ const getHookName = (callee, scopes) => {
|
|
|
14420
14591
|
if (isNodeOfType(strippedCallee, "MemberExpression") && !strippedCallee.computed && isNodeOfType(strippedCallee.property, "Identifier")) return strippedCallee.property.name;
|
|
14421
14592
|
return null;
|
|
14422
14593
|
};
|
|
14423
|
-
const FUNCTION_SCOPE_KINDS = new Set([
|
|
14424
|
-
"function",
|
|
14425
|
-
"arrow-function",
|
|
14426
|
-
"method"
|
|
14427
|
-
]);
|
|
14428
|
-
/**
|
|
14429
|
-
* True for symbols declared at module scope (outside any function
|
|
14430
|
-
* scope). Module-scope bindings don't change between renders so they
|
|
14431
|
-
* don't need to live in dependency arrays.
|
|
14432
|
-
*/
|
|
14433
|
-
const isOutsideAllFunctions = (symbol) => {
|
|
14434
|
-
let scope = symbol.scope;
|
|
14435
|
-
while (scope) {
|
|
14436
|
-
if (FUNCTION_SCOPE_KINDS.has(scope.kind)) return false;
|
|
14437
|
-
if (scope.kind === "module") return true;
|
|
14438
|
-
scope = scope.parent ?? null;
|
|
14439
|
-
}
|
|
14440
|
-
return true;
|
|
14441
|
-
};
|
|
14442
14594
|
//#endregion
|
|
14443
14595
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-messages.ts
|
|
14444
14596
|
const buildMissingDepMessage = (hookName, depName) => `\`${hookName}\` can run with a stale \`${depName}\` & show your users old data.`;
|
|
@@ -14476,25 +14628,6 @@ const resolveExhaustiveDepsSettings = (settings) => {
|
|
|
14476
14628
|
};
|
|
14477
14629
|
};
|
|
14478
14630
|
//#endregion
|
|
14479
|
-
//#region src/plugin/utils/is-ast-descendant.ts
|
|
14480
|
-
/**
|
|
14481
|
-
* True when `inner` is `outer` itself or any descendant in the AST
|
|
14482
|
-
* `parent` chain. Walks `inner.parent` upward and stops at either a
|
|
14483
|
-
* match (`true`) or the chain's root (`false`).
|
|
14484
|
-
*
|
|
14485
|
-
* Was duplicated byte-identical in:
|
|
14486
|
-
* - exhaustive-deps-symbol-stability.ts
|
|
14487
|
-
* - semantic/closure-captures.ts
|
|
14488
|
-
*/
|
|
14489
|
-
const isAstDescendant = (inner, outer) => {
|
|
14490
|
-
let current = inner;
|
|
14491
|
-
while (current) {
|
|
14492
|
-
if (current === outer) return true;
|
|
14493
|
-
current = current.parent ?? null;
|
|
14494
|
-
}
|
|
14495
|
-
return false;
|
|
14496
|
-
};
|
|
14497
|
-
//#endregion
|
|
14498
14631
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-sole-writer-guard.ts
|
|
14499
14632
|
const EQUALITY_BINARY_OPERATORS = new Set(["===", "!=="]);
|
|
14500
14633
|
const getUseStateSetterSymbol = (stateSymbol, scopes) => {
|
|
@@ -21976,7 +22109,7 @@ const unwrapReactHocFunction = (node, scopes) => resolveReactHocFunction(node, s
|
|
|
21976
22109
|
//#endregion
|
|
21977
22110
|
//#region src/plugin/utils/build-same-file-jsx-slot-prop-registry.ts
|
|
21978
22111
|
const REACT_SLOT_TYPE_NAMES = new Set(["ReactNode", "ReactElement"]);
|
|
21979
|
-
const unwrapTopLevelDeclaration = (statement) => {
|
|
22112
|
+
const unwrapTopLevelDeclaration$1 = (statement) => {
|
|
21980
22113
|
if (isNodeOfType(statement, "ExportNamedDeclaration")) return statement.declaration;
|
|
21981
22114
|
if (isNodeOfType(statement, "ExportDefaultDeclaration")) return statement.declaration;
|
|
21982
22115
|
return statement;
|
|
@@ -22013,7 +22146,7 @@ const buildReactSlotTypeEnvironment = (program) => {
|
|
|
22013
22146
|
}
|
|
22014
22147
|
continue;
|
|
22015
22148
|
}
|
|
22016
|
-
const declaration = unwrapTopLevelDeclaration(statement);
|
|
22149
|
+
const declaration = unwrapTopLevelDeclaration$1(statement);
|
|
22017
22150
|
if (!declaration) continue;
|
|
22018
22151
|
if ((isNodeOfType(declaration, "TSInterfaceDeclaration") || isNodeOfType(declaration, "TSTypeAliasDeclaration")) && isNodeOfType(declaration.id, "Identifier")) sameFileTypeDeclarations.set(declaration.id.name, declaration);
|
|
22019
22152
|
if (isNodeOfType(declaration, "TSModuleDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === "JSX") hasLocalJsxNamespace = true;
|
|
@@ -22105,7 +22238,7 @@ const buildSameFileJsxSlotPropRegistry = (program, memoRegistry, scopes) => {
|
|
|
22105
22238
|
if (!isNodeOfType(program, "Program")) return registry;
|
|
22106
22239
|
const environment = buildReactSlotTypeEnvironment(program);
|
|
22107
22240
|
for (const statement of program.body) {
|
|
22108
|
-
const declaration = unwrapTopLevelDeclaration(statement);
|
|
22241
|
+
const declaration = unwrapTopLevelDeclaration$1(statement);
|
|
22109
22242
|
if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) continue;
|
|
22110
22243
|
for (const declarator of declaration.declarations) {
|
|
22111
22244
|
if (!isNodeOfType(declarator.id, "Identifier") || memoRegistry.get(declarator.id.name) !== "memoised" || !declarator.init) continue;
|
|
@@ -29496,7 +29629,7 @@ const isNumericForLoopCounter = (attributeNode, indexName) => {
|
|
|
29496
29629
|
if (forStatement.test && forLoopTestReadsDataLength(forStatement.test)) return false;
|
|
29497
29630
|
return true;
|
|
29498
29631
|
};
|
|
29499
|
-
const EMPTY_NAME_SET = /* @__PURE__ */ new Set();
|
|
29632
|
+
const EMPTY_NAME_SET$1 = /* @__PURE__ */ new Set();
|
|
29500
29633
|
const findIteratorItemNamesOfBinding = (binding) => {
|
|
29501
29634
|
const names = /* @__PURE__ */ new Set();
|
|
29502
29635
|
if (!binding.bindingFunction || !isFunctionLike$1(binding.bindingFunction)) return names;
|
|
@@ -29610,9 +29743,9 @@ const noArrayIndexAsKey = defineRule({
|
|
|
29610
29743
|
const jsxElement = openingElement.parent;
|
|
29611
29744
|
if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
|
|
29612
29745
|
const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
|
|
29613
|
-
const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET;
|
|
29746
|
+
const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET$1;
|
|
29614
29747
|
if (!containsStatefulDescendant(jsxElement, {
|
|
29615
|
-
memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET,
|
|
29748
|
+
memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET$1,
|
|
29616
29749
|
bareIdentifierNames: primitiveItemNames.size > 0 ? new Set([...derivedNames, ...primitiveItemNames]) : derivedNames
|
|
29617
29750
|
})) return;
|
|
29618
29751
|
}
|
|
@@ -30113,6 +30246,9 @@ const findNearestPackageDirectory = (filename) => {
|
|
|
30113
30246
|
const readNearestPackageManifest = (filename) => {
|
|
30114
30247
|
const packageDirectory = findNearestPackageDirectory(filename);
|
|
30115
30248
|
if (!packageDirectory) return null;
|
|
30249
|
+
return readPackageManifest(packageDirectory);
|
|
30250
|
+
};
|
|
30251
|
+
const readPackageManifest = (packageDirectory) => {
|
|
30116
30252
|
const packageJsonPath = path.join(packageDirectory, "package.json");
|
|
30117
30253
|
recordContentProbe(packageJsonPath);
|
|
30118
30254
|
const cached = cachedManifestByPackageDirectory.get(packageDirectory);
|
|
@@ -30491,7 +30627,16 @@ const isRenderPreservingCallArgumentFunction = (node, scopes) => {
|
|
|
30491
30627
|
return parent.arguments.some((argumentNode) => argumentNode === node) && isProvenArrayMapCall(parent, scopes);
|
|
30492
30628
|
};
|
|
30493
30629
|
const isNestedRenderEvidenceBoundary = (node, scopes) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isRenderPreservingCallArgumentFunction(node, scopes);
|
|
30494
|
-
const isRenderOutputExpression = (node, scopes) => node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS);
|
|
30630
|
+
const isRenderOutputExpression = (node, scopes) => node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS) || isReactDomCreatePortalCall(node, scopes);
|
|
30631
|
+
const isReactDomCreatePortalCall = (node, scopes) => {
|
|
30632
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
30633
|
+
const callee = stripParenExpression(node.callee);
|
|
30634
|
+
if (isNodeOfType(callee, "Identifier")) return scopes.symbolFor(callee)?.kind === "import" && getImportedNameFromModule(callee, callee.name, "react-dom") === "createPortal";
|
|
30635
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "createPortal") return false;
|
|
30636
|
+
const symbol = scopes.symbolFor(callee.object);
|
|
30637
|
+
if (!symbol || symbol.kind !== "import") return false;
|
|
30638
|
+
return isDefaultImportFromModule(callee.object, callee.object.name, "react-dom") || isNamespaceImportFromModule(callee.object, callee.object.name, "react-dom");
|
|
30639
|
+
};
|
|
30495
30640
|
const containsRenderOutput$1 = (rootNode, scopes) => {
|
|
30496
30641
|
let hasRenderOutput = false;
|
|
30497
30642
|
walkAst(rootNode, (node) => {
|
|
@@ -35838,7 +35983,7 @@ const isGatedByFalsyInitialState = (node, scopes) => {
|
|
|
35838
35983
|
};
|
|
35839
35984
|
//#endregion
|
|
35840
35985
|
//#region src/plugin/rules/performance/no-hydration-branch-on-browser-global.ts
|
|
35841
|
-
const evaluateEquality = (operator, left, right) => {
|
|
35986
|
+
const evaluateEquality$1 = (operator, left, right) => {
|
|
35842
35987
|
if (operator === "===" || operator === "==") return left === right;
|
|
35843
35988
|
if (operator === "!==" || operator === "!=") return left !== right;
|
|
35844
35989
|
return null;
|
|
@@ -35869,8 +36014,8 @@ const matchBrowserPredicate = (expression, context) => {
|
|
|
35869
36014
|
const browserGlobalName = leftGlobalName && typeof rightString === "string" ? leftGlobalName : rightGlobalName && typeof leftString === "string" ? rightGlobalName : null;
|
|
35870
36015
|
const comparedType = leftGlobalName && typeof rightString === "string" ? rightString : rightGlobalName && typeof leftString === "string" ? leftString : null;
|
|
35871
36016
|
if (!browserGlobalName || !comparedType) return null;
|
|
35872
|
-
const clientResult = evaluateEquality(unwrappedExpression.operator, "object", comparedType);
|
|
35873
|
-
const serverResult = evaluateEquality(unwrappedExpression.operator, "undefined", comparedType);
|
|
36017
|
+
const clientResult = evaluateEquality$1(unwrappedExpression.operator, "object", comparedType);
|
|
36018
|
+
const serverResult = evaluateEquality$1(unwrappedExpression.operator, "undefined", comparedType);
|
|
35874
36019
|
if (clientResult === null || serverResult === null || clientResult === serverResult) return null;
|
|
35875
36020
|
return {
|
|
35876
36021
|
browserGlobalName,
|
|
@@ -41883,18 +42028,107 @@ const getNodeText$1 = (node) => {
|
|
|
41883
42028
|
return value;
|
|
41884
42029
|
});
|
|
41885
42030
|
};
|
|
41886
|
-
const
|
|
41887
|
-
|
|
41888
|
-
|
|
41889
|
-
|
|
41890
|
-
|
|
41891
|
-
|
|
41892
|
-
|
|
41893
|
-
|
|
41894
|
-
|
|
41895
|
-
|
|
42031
|
+
const normalizeAsBoolean = (identity) => ({
|
|
42032
|
+
...identity,
|
|
42033
|
+
booleanNormalization: identity.booleanNormalization === "identity" ? "normalized" : identity.booleanNormalization
|
|
42034
|
+
});
|
|
42035
|
+
const negateBoolean = (identity) => ({
|
|
42036
|
+
...identity,
|
|
42037
|
+
booleanNormalization: identity.booleanNormalization === "negated" ? "normalized" : "negated"
|
|
42038
|
+
});
|
|
42039
|
+
const getLivePropExpressionIdentity = (analysis, context, node, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
42040
|
+
const expression = stripParenExpression(node);
|
|
42041
|
+
if (isNodeOfType(expression, "Identifier")) {
|
|
42042
|
+
const reference = getRef(analysis, expression);
|
|
42043
|
+
const symbol = context.scopes.symbolFor(expression);
|
|
42044
|
+
if (!reference || !symbol) return null;
|
|
42045
|
+
if (isProp(analysis, reference)) return {
|
|
42046
|
+
propSymbolId: symbol.id,
|
|
42047
|
+
memberPath: [],
|
|
42048
|
+
booleanNormalization: "identity"
|
|
42049
|
+
};
|
|
42050
|
+
if (visitedSymbolIds.has(symbol.id) || symbol.kind !== "const") return null;
|
|
42051
|
+
visitedSymbolIds.add(symbol.id);
|
|
42052
|
+
const initializer = getDirectConstInitializer(symbol);
|
|
42053
|
+
if (initializer) return getLivePropExpressionIdentity(analysis, context, initializer, visitedSymbolIds);
|
|
42054
|
+
return getUpstreamRefs(analysis, reference).some((upstreamReference) => isProp(analysis, upstreamReference)) ? {
|
|
42055
|
+
propSymbolId: symbol.id,
|
|
42056
|
+
memberPath: [],
|
|
42057
|
+
booleanNormalization: "identity"
|
|
42058
|
+
} : null;
|
|
42059
|
+
}
|
|
42060
|
+
if (isNodeOfType(expression, "MemberExpression")) {
|
|
42061
|
+
const propertyName = getStaticMemberPropertyName(expression);
|
|
42062
|
+
if (!propertyName) return null;
|
|
42063
|
+
const objectIdentity = getLivePropExpressionIdentity(analysis, context, expression.object, visitedSymbolIds);
|
|
42064
|
+
if (!objectIdentity) return null;
|
|
42065
|
+
return {
|
|
42066
|
+
...objectIdentity,
|
|
42067
|
+
memberPath: [...objectIdentity.memberPath, propertyName]
|
|
42068
|
+
};
|
|
42069
|
+
}
|
|
42070
|
+
if (isNodeOfType(expression, "CallExpression") && isNodeOfType(expression.callee, "Identifier") && expression.callee.name === "Boolean" && context.scopes.isGlobalReference(expression.callee) && expression.arguments?.length === 1) {
|
|
42071
|
+
const argument = expression.arguments[0];
|
|
42072
|
+
const argumentIdentity = getLivePropExpressionIdentity(analysis, context, argument, visitedSymbolIds);
|
|
42073
|
+
return argumentIdentity ? normalizeAsBoolean(argumentIdentity) : null;
|
|
42074
|
+
}
|
|
42075
|
+
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") {
|
|
42076
|
+
const argumentIdentity = getLivePropExpressionIdentity(analysis, context, expression.argument, visitedSymbolIds);
|
|
42077
|
+
return argumentIdentity ? negateBoolean(argumentIdentity) : null;
|
|
42078
|
+
}
|
|
42079
|
+
return null;
|
|
42080
|
+
};
|
|
42081
|
+
const isMountSnapshotInitializer = (context, node) => {
|
|
42082
|
+
if (!isReactApiCall(node, "useMemo", context.scopes, { resolveNamedAliases: true }) || !isNodeOfType(node, "CallExpression")) return false;
|
|
42083
|
+
const dependencies = node.arguments?.[1];
|
|
42084
|
+
return isNodeOfType(dependencies, "ArrayExpression") && dependencies.elements.length === 0;
|
|
41896
42085
|
};
|
|
41897
|
-
const
|
|
42086
|
+
const isMountSnapshotBinding = (context, identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
42087
|
+
const symbol = context.scopes.symbolFor(identifier);
|
|
42088
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
42089
|
+
visitedSymbolIds.add(symbol.id);
|
|
42090
|
+
const initializer = getDirectConstInitializer(symbol);
|
|
42091
|
+
if (!initializer) return false;
|
|
42092
|
+
if (isMountSnapshotInitializer(context, initializer)) return true;
|
|
42093
|
+
const expression = stripParenExpression(initializer);
|
|
42094
|
+
return isNodeOfType(expression, "Identifier") && isMountSnapshotBinding(context, expression, visitedSymbolIds);
|
|
42095
|
+
};
|
|
42096
|
+
const isConstantBooleanExpression = (context, node, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
42097
|
+
const expression = stripParenExpression(node);
|
|
42098
|
+
if (isNodeOfType(expression, "Literal")) return true;
|
|
42099
|
+
if (isNodeOfType(expression, "Identifier")) {
|
|
42100
|
+
const symbol = context.scopes.symbolFor(expression);
|
|
42101
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
42102
|
+
visitedSymbolIds.add(symbol.id);
|
|
42103
|
+
const initializer = getDirectConstInitializer(symbol);
|
|
42104
|
+
return Boolean(initializer && isConstantBooleanExpression(context, initializer, visitedSymbolIds));
|
|
42105
|
+
}
|
|
42106
|
+
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") return isConstantBooleanExpression(context, expression.argument, visitedSymbolIds);
|
|
42107
|
+
if (isNodeOfType(expression, "CallExpression") && isNodeOfType(expression.callee, "Identifier") && expression.callee.name === "Boolean" && context.scopes.isGlobalReference(expression.callee) && expression.arguments?.length === 1) return isConstantBooleanExpression(context, expression.arguments[0], visitedSymbolIds);
|
|
42108
|
+
return false;
|
|
42109
|
+
};
|
|
42110
|
+
const isProvenLiveBinding = (analysis, context, identifier) => {
|
|
42111
|
+
const reference = getRef(analysis, identifier);
|
|
42112
|
+
const symbol = context.scopes.symbolFor(identifier);
|
|
42113
|
+
if (!reference || !symbol || symbol.kind !== "const" || isOutsideAllFunctions(symbol)) return false;
|
|
42114
|
+
const initializer = symbol.initializer;
|
|
42115
|
+
if (!initializer || isMountSnapshotBinding(context, identifier)) return false;
|
|
42116
|
+
if (getDownstreamRefs(analysis, initializer).some((initializerReference) => isRefCurrent(initializerReference))) return false;
|
|
42117
|
+
return !isConstantBooleanExpression(context, initializer);
|
|
42118
|
+
};
|
|
42119
|
+
const areSameProvenLiveBinding = (analysis, context, left, right) => {
|
|
42120
|
+
const leftExpression = stripParenExpression(left);
|
|
42121
|
+
const rightExpression = stripParenExpression(right);
|
|
42122
|
+
if (!isNodeOfType(leftExpression, "Identifier") || !isNodeOfType(rightExpression, "Identifier")) return false;
|
|
42123
|
+
const leftSymbol = context.scopes.symbolFor(leftExpression);
|
|
42124
|
+
const rightSymbol = context.scopes.symbolFor(rightExpression);
|
|
42125
|
+
return Boolean(leftSymbol && leftSymbol === rightSymbol && isProvenLiveBinding(analysis, context, leftExpression));
|
|
42126
|
+
};
|
|
42127
|
+
const haveSameLivePropExpressionIdentity = (left, right) => {
|
|
42128
|
+
if (left.propSymbolId !== right.propSymbolId || left.booleanNormalization !== right.booleanNormalization || left.memberPath.length !== right.memberPath.length) return false;
|
|
42129
|
+
return left.memberPath.every((propertyName, index) => propertyName === right.memberPath[index]);
|
|
42130
|
+
};
|
|
42131
|
+
const isSetStateToInitialValue = (analysis, context, setterRef) => {
|
|
41898
42132
|
const callExpr = getCallExpr(setterRef);
|
|
41899
42133
|
if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) return false;
|
|
41900
42134
|
const setStateToValue = callExpr.arguments?.[0];
|
|
@@ -41905,7 +42139,12 @@ const isSetStateToInitialValue = (analysis, setterRef) => {
|
|
|
41905
42139
|
if (isUndefinedNode(setStateToValue) && isUndefinedNode(stateInitialValue)) return true;
|
|
41906
42140
|
if (setStateToValue == null && stateInitialValue == null) return true;
|
|
41907
42141
|
if (setStateToValue && !stateInitialValue || !setStateToValue && stateInitialValue) return false;
|
|
41908
|
-
if (stateInitialValue &&
|
|
42142
|
+
if (stateInitialValue && setStateToValue) {
|
|
42143
|
+
const initialLivePropIdentity = getLivePropExpressionIdentity(analysis, context, stateInitialValue);
|
|
42144
|
+
const nextLivePropIdentity = getLivePropExpressionIdentity(analysis, context, setStateToValue);
|
|
42145
|
+
if (initialLivePropIdentity && nextLivePropIdentity && haveSameLivePropExpressionIdentity(initialLivePropIdentity, nextLivePropIdentity)) return false;
|
|
42146
|
+
if (areSameProvenLiveBinding(analysis, context, stateInitialValue, setStateToValue)) return false;
|
|
42147
|
+
}
|
|
41909
42148
|
return getNodeText$1(setStateToValue) === getNodeText$1(stateInitialValue);
|
|
41910
42149
|
};
|
|
41911
42150
|
const countUseStates = (analysis, componentNode) => {
|
|
@@ -41914,10 +42153,10 @@ const countUseStates = (analysis, componentNode) => {
|
|
|
41914
42153
|
for (const ref of getDownstreamRefs(analysis, componentNode)) if (isState(analysis, ref)) stateVariables.add(ref.resolved);
|
|
41915
42154
|
return stateVariables.size;
|
|
41916
42155
|
};
|
|
41917
|
-
const findPropUsedToResetAllState = (analysis, effectFnRefs, depsRefs, useEffectNode, effectFn) => {
|
|
42156
|
+
const findPropUsedToResetAllState = (analysis, context, effectFnRefs, depsRefs, useEffectNode, effectFn) => {
|
|
41918
42157
|
const stateSetterRefs = effectFnRefs.filter((ref) => isSyncStateSetterCall(analysis, ref, effectFn));
|
|
41919
42158
|
if (stateSetterRefs.length === 0) return null;
|
|
41920
|
-
if (!stateSetterRefs.every((ref) => isSetStateToInitialValue(analysis, ref))) return null;
|
|
42159
|
+
if (!stateSetterRefs.every((ref) => isSetStateToInitialValue(analysis, context, ref))) return null;
|
|
41921
42160
|
if (stateSetterRefs.every((setterRef) => effectFnRefs.some((otherRef) => otherRef !== setterRef && otherRef.resolved === setterRef.resolved && Boolean(getCallExpr(otherRef)) && !isSyncStateSetterCall(analysis, otherRef, effectFn)))) return null;
|
|
41922
42161
|
const containing = findContainingNode(analysis, useEffectNode);
|
|
41923
42162
|
if (new Set(stateSetterRefs.map((setterRef) => setterRef.resolved)).size !== countUseStates(analysis, containing)) return null;
|
|
@@ -41941,7 +42180,7 @@ const noResetAllStateOnPropChange = defineRule({
|
|
|
41941
42180
|
if (containing && isCustomHook(containing)) return;
|
|
41942
42181
|
const effectFn = getEffectFn(analysis, node);
|
|
41943
42182
|
if (!effectFn) return;
|
|
41944
|
-
if (!findPropUsedToResetAllState(analysis, effectFnRefs, depsRefs, node, effectFn)) return;
|
|
42183
|
+
if (!findPropUsedToResetAllState(analysis, context, effectFnRefs, depsRefs, node, effectFn)) return;
|
|
41945
42184
|
context.report({
|
|
41946
42185
|
node,
|
|
41947
42186
|
message: `Your users briefly see stale state when a prop changes because this useEffect clears all state.`
|
|
@@ -45744,19 +45983,726 @@ const nosqlInjectionRisk = defineRule({
|
|
|
45744
45983
|
})
|
|
45745
45984
|
});
|
|
45746
45985
|
//#endregion
|
|
45986
|
+
//#region src/plugin/utils/export-all-adds-runtime-values.ts
|
|
45987
|
+
const getExportedName = (node) => {
|
|
45988
|
+
if (!node) return null;
|
|
45989
|
+
if (isNodeOfType(node, "Identifier")) return node.name;
|
|
45990
|
+
if (isNodeOfType(node, "Literal") && typeof node.value === "string") return node.value;
|
|
45991
|
+
return null;
|
|
45992
|
+
};
|
|
45993
|
+
const programHasRuntimeNamedExports = (filePath, program, visitedFilePaths) => {
|
|
45994
|
+
if (!isNodeOfType(program, "Program")) return true;
|
|
45995
|
+
for (const statement of program.body) {
|
|
45996
|
+
if (isNodeOfType(statement, "ExportDefaultDeclaration")) continue;
|
|
45997
|
+
if (isNodeOfType(statement, "ExportAllDeclaration")) {
|
|
45998
|
+
if (statement.exportKind === "type") continue;
|
|
45999
|
+
if (statement.exported) return true;
|
|
46000
|
+
if (typeof statement.source.value !== "string") return true;
|
|
46001
|
+
if (exportAllAddsRuntimeValues(filePath, statement.source.value, visitedFilePaths)) return true;
|
|
46002
|
+
continue;
|
|
46003
|
+
}
|
|
46004
|
+
if (!isNodeOfType(statement, "ExportNamedDeclaration")) continue;
|
|
46005
|
+
if (statement.exportKind === "type") continue;
|
|
46006
|
+
if (statement.declaration) {
|
|
46007
|
+
if (statement.declaration.type !== "TSInterfaceDeclaration" && statement.declaration.type !== "TSTypeAliasDeclaration" && statement.declaration.type !== "TSDeclareFunction") return true;
|
|
46008
|
+
continue;
|
|
46009
|
+
}
|
|
46010
|
+
if (isEverySpecifierInlineType(statement.specifiers, "ExportSpecifier", "exportKind")) continue;
|
|
46011
|
+
for (const specifier of statement.specifiers) {
|
|
46012
|
+
if (!isNodeOfType(specifier, "ExportSpecifier") || specifier.exportKind === "type") continue;
|
|
46013
|
+
if (getExportedName(specifier.exported) !== "default") return true;
|
|
46014
|
+
}
|
|
46015
|
+
}
|
|
46016
|
+
return false;
|
|
46017
|
+
};
|
|
46018
|
+
const exportAllAddsRuntimeValues = (importerFilePath, source, visitedFilePaths = /* @__PURE__ */ new Set()) => {
|
|
46019
|
+
const resolvedFilePath = resolveRelativeImportPath(importerFilePath, source);
|
|
46020
|
+
if (!resolvedFilePath) return true;
|
|
46021
|
+
if (path.extname(resolvedFilePath) === ".cjs" || resolvedFilePath.endsWith(".cts")) return true;
|
|
46022
|
+
if (visitedFilePaths.has(resolvedFilePath)) return true;
|
|
46023
|
+
visitedFilePaths.add(resolvedFilePath);
|
|
46024
|
+
const program = parseSourceFile(resolvedFilePath);
|
|
46025
|
+
if (!program) return true;
|
|
46026
|
+
return programHasRuntimeNamedExports(resolvedFilePath, program, visitedFilePaths);
|
|
46027
|
+
};
|
|
46028
|
+
//#endregion
|
|
45747
46029
|
//#region src/plugin/utils/is-framework-route-or-special-filename.ts
|
|
45748
|
-
const
|
|
45749
|
-
const
|
|
45750
|
-
|
|
45751
|
-
|
|
45752
|
-
|
|
45753
|
-
|
|
45754
|
-
|
|
45755
|
-
const
|
|
46030
|
+
const NEXT_APP_ROUTE_FILE_PATTERN = new RegExp(`^(page|layout|loading|error|not-found|template|default|global-error|route)\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
|
|
46031
|
+
const NEXT_PAGES_ROUTE_FILE_PATTERN = new RegExp(`^(_app|_document|_error|_meta)\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
|
|
46032
|
+
const EXPO_ROUTE_FILE_PATTERN = new RegExp(`^(_layout|\\+html|\\+not-found|\\+native-intent)\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
|
|
46033
|
+
const TANSTACK_ROUTE_FILE_PATTERN = new RegExp(`(?:^__root|\\.lazy)\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
|
|
46034
|
+
const REACT_ROUTER_FILE_PATTERN = new RegExp(`^(root|entry\\.client|entry\\.server)\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
|
|
46035
|
+
const isInNextDirectory = (context, directoryPath) => {
|
|
46036
|
+
if (isInProjectDirectory(context, directoryPath)) return true;
|
|
46037
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
46038
|
+
if (path.isAbsolute(filename)) return false;
|
|
46039
|
+
return filename.startsWith(`${directoryPath}/`) || filename.includes(`/${directoryPath}/`);
|
|
46040
|
+
};
|
|
46041
|
+
const isInExpoRouteDirectory = (context) => {
|
|
46042
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
46043
|
+
const configuredRootDirectory = getReactDoctorStringSetting(context.settings, "rootDirectory");
|
|
46044
|
+
const packageDirectory = path.isAbsolute(filename) ? findNearestPackageDirectory(filename) : null;
|
|
46045
|
+
const projectRelativeFilename = getProjectRelativeFilename(filename, configuredRootDirectory ?? packageDirectory ?? void 0);
|
|
46046
|
+
const relativeFilename = projectRelativeFilename === filename && path.isAbsolute(filename) ? filename.split("/").slice(2).join("/") : projectRelativeFilename;
|
|
46047
|
+
return relativeFilename.startsWith("app/") || relativeFilename.startsWith("src/app/");
|
|
46048
|
+
};
|
|
46049
|
+
const isFrameworkRouteOrSpecialFilename = (context, runtime) => {
|
|
46050
|
+
const rawFilename = context.filename;
|
|
45756
46051
|
if (!rawFilename) return false;
|
|
45757
|
-
if (isNextjsMetadataImageRouteFilename(rawFilename)) return true;
|
|
45758
46052
|
const basename = path.basename(rawFilename);
|
|
45759
|
-
return
|
|
46053
|
+
if (runtime === "next") return isInNextDirectory(context, "app") && (isNextjsMetadataImageRouteFilename(rawFilename) || NEXT_APP_ROUTE_FILE_PATTERN.test(basename)) || isInNextDirectory(context, "pages") && NEXT_PAGES_ROUTE_FILE_PATTERN.test(basename);
|
|
46054
|
+
if (runtime === "expo") return isInExpoRouteDirectory(context) && EXPO_ROUTE_FILE_PATTERN.test(basename);
|
|
46055
|
+
if (runtime === "tanstack") return TANSTACK_ROUTE_FILE_PATTERN.test(basename);
|
|
46056
|
+
if (runtime === "react-router" || runtime === "remix") return REACT_ROUTER_FILE_PATTERN.test(basename);
|
|
46057
|
+
return false;
|
|
46058
|
+
};
|
|
46059
|
+
//#endregion
|
|
46060
|
+
//#region src/plugin/utils/function-has-react-element-return-type.ts
|
|
46061
|
+
const isReactNamespaceBinding = (node) => {
|
|
46062
|
+
if (!isNodeOfType(node, "Identifier")) return false;
|
|
46063
|
+
const binding = getImportBindingForName(node, node.name);
|
|
46064
|
+
return Boolean(binding && binding.source === "react" && (binding.isNamespace || binding.exportedName === "default"));
|
|
46065
|
+
};
|
|
46066
|
+
const hasLocalJsxNamespace = (node) => {
|
|
46067
|
+
const program = findProgramRoot(node);
|
|
46068
|
+
if (!program || !isNodeOfType(program, "Program")) return false;
|
|
46069
|
+
return program.body.some((statement) => isNodeOfType(statement, "TSModuleDeclaration") && isNodeOfType(statement.id, "Identifier") && statement.id.name === "JSX");
|
|
46070
|
+
};
|
|
46071
|
+
const isReactElementTypeReference = (node) => {
|
|
46072
|
+
if (!isNodeOfType(node, "TSTypeReference")) return false;
|
|
46073
|
+
const typeName = node.typeName;
|
|
46074
|
+
if (isNodeOfType(typeName, "Identifier")) return getImportedNameFromModule(typeName, typeName.name, "react") === "ReactElement";
|
|
46075
|
+
if (!isNodeOfType(typeName, "TSQualifiedName")) return false;
|
|
46076
|
+
if (!isNodeOfType(typeName.right, "Identifier")) return false;
|
|
46077
|
+
if (typeName.right.name === "ReactElement" && isReactNamespaceBinding(typeName.left)) return true;
|
|
46078
|
+
if (typeName.right.name !== "Element") return false;
|
|
46079
|
+
if (isNodeOfType(typeName.left, "Identifier")) {
|
|
46080
|
+
if (typeName.left.name !== "JSX") return false;
|
|
46081
|
+
const binding = getImportBindingForName(typeName.left, typeName.left.name);
|
|
46082
|
+
if (!binding) return !hasLocalJsxNamespace(typeName.left);
|
|
46083
|
+
return binding.source === "react" && binding.exportedName === "JSX";
|
|
46084
|
+
}
|
|
46085
|
+
return isNodeOfType(typeName.left, "TSQualifiedName") && isNodeOfType(typeName.left.right, "Identifier") && typeName.left.right.name === "JSX" && isReactNamespaceBinding(typeName.left.left);
|
|
46086
|
+
};
|
|
46087
|
+
const containsReactElementType = (node) => {
|
|
46088
|
+
if (isNodeOfType(node, "TSUnionType")) return node.types.some((member) => containsReactElementType(member));
|
|
46089
|
+
return isReactElementTypeReference(node);
|
|
46090
|
+
};
|
|
46091
|
+
const functionHasReactElementReturnType = (functionNode) => {
|
|
46092
|
+
const returnType = Reflect.get(functionNode, "returnType");
|
|
46093
|
+
if (!isNodeOfType(returnType, "TSTypeAnnotation")) return false;
|
|
46094
|
+
return containsReactElementType(returnType.typeAnnotation);
|
|
46095
|
+
};
|
|
46096
|
+
//#endregion
|
|
46097
|
+
//#region src/plugin/constants/fast-refresh.ts
|
|
46098
|
+
const FAST_REFRESH_CONFIG_FILENAMES = [
|
|
46099
|
+
"vite.config.ts",
|
|
46100
|
+
"vite.config.mts",
|
|
46101
|
+
"vite.config.cts",
|
|
46102
|
+
"vite.config.js",
|
|
46103
|
+
"vite.config.mjs",
|
|
46104
|
+
"vite.config.cjs",
|
|
46105
|
+
"webpack.config.ts",
|
|
46106
|
+
"webpack.config.js",
|
|
46107
|
+
"webpack.config.mjs",
|
|
46108
|
+
"webpack.config.cjs",
|
|
46109
|
+
"rsbuild.config.ts",
|
|
46110
|
+
"rsbuild.config.js",
|
|
46111
|
+
"rspack.config.ts",
|
|
46112
|
+
"rspack.config.js"
|
|
46113
|
+
];
|
|
46114
|
+
const MINIMUM_FAST_REFRESH_VERSIONS = {
|
|
46115
|
+
dumi: {
|
|
46116
|
+
major: 2,
|
|
46117
|
+
minor: 0
|
|
46118
|
+
},
|
|
46119
|
+
expo: {
|
|
46120
|
+
major: 36,
|
|
46121
|
+
minor: 0
|
|
46122
|
+
},
|
|
46123
|
+
gatsby: {
|
|
46124
|
+
major: 2,
|
|
46125
|
+
minor: 31
|
|
46126
|
+
},
|
|
46127
|
+
next: {
|
|
46128
|
+
major: 9,
|
|
46129
|
+
minor: 4
|
|
46130
|
+
},
|
|
46131
|
+
parcel: {
|
|
46132
|
+
major: 2,
|
|
46133
|
+
minor: 0
|
|
46134
|
+
},
|
|
46135
|
+
reactForGatsbyTwo: {
|
|
46136
|
+
major: 17,
|
|
46137
|
+
minor: 0
|
|
46138
|
+
},
|
|
46139
|
+
reactNative: {
|
|
46140
|
+
major: 0,
|
|
46141
|
+
minor: 61
|
|
46142
|
+
},
|
|
46143
|
+
reactScripts: {
|
|
46144
|
+
major: 4,
|
|
46145
|
+
minor: 0
|
|
46146
|
+
},
|
|
46147
|
+
storybookReact: {
|
|
46148
|
+
major: 6,
|
|
46149
|
+
minor: 1
|
|
46150
|
+
}
|
|
46151
|
+
};
|
|
46152
|
+
//#endregion
|
|
46153
|
+
//#region src/plugin/utils/get-fast-refresh-file-status.ts
|
|
46154
|
+
const INTEGRATION_IMPORTS = new Map([
|
|
46155
|
+
["@vitejs/plugin-react", {
|
|
46156
|
+
importedNames: null,
|
|
46157
|
+
requiresViteDevelopmentServer: true,
|
|
46158
|
+
runtime: "generic"
|
|
46159
|
+
}],
|
|
46160
|
+
["@vitejs/plugin-react-swc", {
|
|
46161
|
+
importedNames: null,
|
|
46162
|
+
requiresViteDevelopmentServer: true,
|
|
46163
|
+
runtime: "generic"
|
|
46164
|
+
}],
|
|
46165
|
+
["@pmmmwh/react-refresh-webpack-plugin", {
|
|
46166
|
+
importedNames: null,
|
|
46167
|
+
runtime: "generic"
|
|
46168
|
+
}],
|
|
46169
|
+
["@react-router/dev/vite", {
|
|
46170
|
+
importedNames: new Set(["reactRouter"]),
|
|
46171
|
+
requiresViteDevelopmentServer: true,
|
|
46172
|
+
runtime: "react-router"
|
|
46173
|
+
}],
|
|
46174
|
+
["@remix-run/dev", {
|
|
46175
|
+
importedNames: new Set(["vitePlugin"]),
|
|
46176
|
+
requiresViteDevelopmentServer: true,
|
|
46177
|
+
runtime: "remix"
|
|
46178
|
+
}],
|
|
46179
|
+
["@rsbuild/plugin-react", {
|
|
46180
|
+
importedNames: new Set(["pluginReact"]),
|
|
46181
|
+
runtime: "generic"
|
|
46182
|
+
}],
|
|
46183
|
+
["@rspack/plugin-react-refresh", {
|
|
46184
|
+
importedNames: null,
|
|
46185
|
+
runtime: "generic"
|
|
46186
|
+
}],
|
|
46187
|
+
["@rozenite/vite-plugin", {
|
|
46188
|
+
importedNames: new Set(["rozenitePlugin"]),
|
|
46189
|
+
requiresViteDevelopmentServer: true,
|
|
46190
|
+
runtime: "generic"
|
|
46191
|
+
}],
|
|
46192
|
+
["@tanstack/react-start/plugin/vite", {
|
|
46193
|
+
importedNames: new Set(["tanstackStart"]),
|
|
46194
|
+
requiresViteDevelopmentServer: true,
|
|
46195
|
+
runtime: "tanstack"
|
|
46196
|
+
}]
|
|
46197
|
+
]);
|
|
46198
|
+
const REGISTERED_INTEGRATION_RUNTIME_PRECEDENCE = [
|
|
46199
|
+
"tanstack",
|
|
46200
|
+
"remix",
|
|
46201
|
+
"react-router",
|
|
46202
|
+
"generic"
|
|
46203
|
+
];
|
|
46204
|
+
const cachedLocalStatusByManifest = /* @__PURE__ */ new WeakMap();
|
|
46205
|
+
const cachedWorkspaceIndexByManifest = /* @__PURE__ */ new WeakMap();
|
|
46206
|
+
const INACTIVE_STATUS = {
|
|
46207
|
+
isActive: false,
|
|
46208
|
+
runtime: "generic"
|
|
46209
|
+
};
|
|
46210
|
+
const WORKSPACE_IGNORED_DIRECTORY_NAMES = new Set([
|
|
46211
|
+
".git",
|
|
46212
|
+
".next",
|
|
46213
|
+
"build",
|
|
46214
|
+
"coverage",
|
|
46215
|
+
"dist",
|
|
46216
|
+
"node_modules",
|
|
46217
|
+
"out"
|
|
46218
|
+
]);
|
|
46219
|
+
const parseVersion = (version) => {
|
|
46220
|
+
if (typeof version !== "string") return null;
|
|
46221
|
+
const match = version.match(/(?:^|[^\d])(\d+)(?:\.(\d+))?/);
|
|
46222
|
+
if (!match) return null;
|
|
46223
|
+
return {
|
|
46224
|
+
major: Number(match[1]),
|
|
46225
|
+
minor: Number(match[2] ?? 0)
|
|
46226
|
+
};
|
|
46227
|
+
};
|
|
46228
|
+
const isVersionAtLeast = (version, minimum) => {
|
|
46229
|
+
const parsed = parseVersion(version);
|
|
46230
|
+
if (!parsed) return false;
|
|
46231
|
+
return parsed.major > minimum.major || parsed.major === minimum.major && parsed.minor >= minimum.minor;
|
|
46232
|
+
};
|
|
46233
|
+
const getDependencyVersion = (manifest, dependencyName) => manifest.dependencies?.[dependencyName] ?? manifest.devDependencies?.[dependencyName] ?? manifest.peerDependencies?.[dependencyName] ?? manifest.optionalDependencies?.[dependencyName];
|
|
46234
|
+
const getRuntimeDependencyVersion = (manifest, dependencyName) => manifest.dependencies?.[dependencyName] ?? manifest.optionalDependencies?.[dependencyName];
|
|
46235
|
+
const getOwnedDependencyVersion = (manifest, dependencyName, developmentCommandPattern) => {
|
|
46236
|
+
const runtimeVersion = getRuntimeDependencyVersion(manifest, dependencyName);
|
|
46237
|
+
if (runtimeVersion !== void 0) return runtimeVersion;
|
|
46238
|
+
return Object.values(manifest.scripts ?? {}).some((script) => typeof script === "string" && developmentCommandPattern.test(script)) ? manifest.devDependencies?.[dependencyName] : void 0;
|
|
46239
|
+
};
|
|
46240
|
+
const hasParcelBrowserEntry = (manifest) => {
|
|
46241
|
+
if (typeof manifest.source === "string" && manifest.source.endsWith(".html")) return true;
|
|
46242
|
+
return Object.values(manifest.scripts ?? {}).some((script) => typeof script === "string" && /(?:^|\s)parcel(?:\s+serve)?\s+[^\n]*\.html(?:\s|$)/.test(script));
|
|
46243
|
+
};
|
|
46244
|
+
const hasParcelDevelopmentCommand = (manifest) => Object.values(manifest.scripts ?? {}).some((script) => typeof script === "string" && /(?:^|[\s;&|'\\"])parcel(?=\s|$)(?!\s+(?:build|help|watch|--help|--version|-h|-V)(?:\s|$))/.test(script) && !/(?:^|\s)--no-hmr(?:\s|$)/.test(script));
|
|
46245
|
+
const hasOwnedDevelopmentCommand = (manifest, commandPattern) => Object.values(manifest.scripts ?? {}).some((script) => typeof script === "string" && commandPattern.test(script));
|
|
46246
|
+
const hasViteDevelopmentCommand = (manifest) => Object.values(manifest.scripts ?? {}).some((script) => {
|
|
46247
|
+
if (typeof script !== "string") return false;
|
|
46248
|
+
return [...script.matchAll(/(?:^|[\s;&|'\\"])vite(?:\s+(build|preview|test)\b)?/g)].some((match) => match[1] === void 0);
|
|
46249
|
+
});
|
|
46250
|
+
const hasViteBrowserEntry = (manifest, packageDirectory) => {
|
|
46251
|
+
if (typeof manifest.source === "string" && manifest.source.endsWith(".html")) return true;
|
|
46252
|
+
const browserEntryPath = path.join(packageDirectory, "index.html");
|
|
46253
|
+
recordExistenceProbe(browserEntryPath);
|
|
46254
|
+
return fs.existsSync(browserEntryPath);
|
|
46255
|
+
};
|
|
46256
|
+
const getBuiltInStatus = (manifest) => {
|
|
46257
|
+
if (isVersionAtLeast(getOwnedDependencyVersion(manifest, "next", /(?:^|\s)next\s+dev(?:\s|$)/), MINIMUM_FAST_REFRESH_VERSIONS.next)) return {
|
|
46258
|
+
isActive: true,
|
|
46259
|
+
runtime: "next"
|
|
46260
|
+
};
|
|
46261
|
+
const gatsbyDependency = getOwnedDependencyVersion(manifest, "gatsby", /(?:^|\s)gatsby\s+develop(?:\s|$)/);
|
|
46262
|
+
const gatsbyVersion = parseVersion(gatsbyDependency);
|
|
46263
|
+
const hasGatsbyFastRefresh = gatsbyVersion !== null && (gatsbyVersion.major >= 3 || isVersionAtLeast(gatsbyDependency, MINIMUM_FAST_REFRESH_VERSIONS.gatsby) && isVersionAtLeast(getDependencyVersion(manifest, "react"), MINIMUM_FAST_REFRESH_VERSIONS.reactForGatsbyTwo));
|
|
46264
|
+
if (isVersionAtLeast(getOwnedDependencyVersion(manifest, "react-scripts", /(?:^|\s)react-scripts\s+start(?:\s|$)/), MINIMUM_FAST_REFRESH_VERSIONS.reactScripts) || hasGatsbyFastRefresh || isVersionAtLeast(getDependencyVersion(manifest, "parcel"), MINIMUM_FAST_REFRESH_VERSIONS.parcel) && declaresDependency(manifest, "react") && hasParcelDevelopmentCommand(manifest) && hasParcelBrowserEntry(manifest)) return {
|
|
46265
|
+
isActive: true,
|
|
46266
|
+
runtime: "generic"
|
|
46267
|
+
};
|
|
46268
|
+
if (isVersionAtLeast(getOwnedDependencyVersion(manifest, "expo", /(?:^|\s)expo\s+start(?:\s|$)/), MINIMUM_FAST_REFRESH_VERSIONS.expo) || isVersionAtLeast(getOwnedDependencyVersion(manifest, "react-native", /(?:^|\s)react-native\s+start(?:\s|$)/), MINIMUM_FAST_REFRESH_VERSIONS.reactNative)) return {
|
|
46269
|
+
isActive: true,
|
|
46270
|
+
runtime: "expo"
|
|
46271
|
+
};
|
|
46272
|
+
if (isVersionAtLeast(getOwnedDependencyVersion(manifest, "dumi", /(?:^|\s)dumi\s+dev(?:\s|$)/), MINIMUM_FAST_REFRESH_VERSIONS.dumi)) return {
|
|
46273
|
+
isActive: true,
|
|
46274
|
+
runtime: "generic"
|
|
46275
|
+
};
|
|
46276
|
+
return null;
|
|
46277
|
+
};
|
|
46278
|
+
const getIntegrationLocalNames = (program, scopes) => {
|
|
46279
|
+
const localBindings = /* @__PURE__ */ new Map();
|
|
46280
|
+
const registerLocalName = (source, importedName, localIdentifier) => {
|
|
46281
|
+
const integration = INTEGRATION_IMPORTS.get(source);
|
|
46282
|
+
if (!integration) return;
|
|
46283
|
+
if (integration.importedNames === null ? importedName === null || importedName === "default" : importedName !== null && integration.importedNames.has(importedName)) {
|
|
46284
|
+
const symbol = scopes.symbolFor(localIdentifier);
|
|
46285
|
+
if (symbol) localBindings.set(symbol, integration);
|
|
46286
|
+
}
|
|
46287
|
+
};
|
|
46288
|
+
walkAst(program, (node) => {
|
|
46289
|
+
if (isNodeOfType(node, "ImportDeclaration")) {
|
|
46290
|
+
const source = node.source.value;
|
|
46291
|
+
if (typeof source !== "string") return;
|
|
46292
|
+
for (const specifier of node.specifiers) {
|
|
46293
|
+
if (isNodeOfType(specifier, "ImportDefaultSpecifier") || isNodeOfType(specifier, "ImportNamespaceSpecifier")) {
|
|
46294
|
+
registerLocalName(source, null, specifier.local);
|
|
46295
|
+
continue;
|
|
46296
|
+
}
|
|
46297
|
+
registerLocalName(source, getImportedName(specifier) ?? null, specifier.local);
|
|
46298
|
+
}
|
|
46299
|
+
return;
|
|
46300
|
+
}
|
|
46301
|
+
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return;
|
|
46302
|
+
const initializer = node.init;
|
|
46303
|
+
if (!isNodeOfType(initializer, "CallExpression") || !isNodeOfType(initializer.callee, "Identifier") || initializer.callee.name !== "require") return;
|
|
46304
|
+
const sourceArgument = initializer.arguments[0];
|
|
46305
|
+
if (!sourceArgument || !isNodeOfType(sourceArgument, "Literal")) return;
|
|
46306
|
+
const source = sourceArgument.value;
|
|
46307
|
+
if (typeof source !== "string") return;
|
|
46308
|
+
if (isNodeOfType(node.id, "Identifier")) {
|
|
46309
|
+
registerLocalName(source, null, node.id);
|
|
46310
|
+
return;
|
|
46311
|
+
}
|
|
46312
|
+
if (!isNodeOfType(node.id, "ObjectPattern")) return;
|
|
46313
|
+
for (const property of node.id.properties) {
|
|
46314
|
+
if (!isNodeOfType(property, "Property") || !isNodeOfType(property.value, "Identifier")) continue;
|
|
46315
|
+
registerLocalName(source, isNodeOfType(property.key, "Identifier") ? property.key.name : isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" ? property.key.value : null, property.value);
|
|
46316
|
+
}
|
|
46317
|
+
});
|
|
46318
|
+
return localBindings;
|
|
46319
|
+
};
|
|
46320
|
+
const isModuleExportsAssignment = (node) => isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && isNodeOfType(node.left.object, "Identifier") && node.left.object.name === "module" && isNodeOfType(node.left.property, "Identifier") && node.left.property.name === "exports";
|
|
46321
|
+
const getExportedBindings = (program, scopes) => {
|
|
46322
|
+
const exportedBindings = /* @__PURE__ */ new Set();
|
|
46323
|
+
walkAst(program, (node) => {
|
|
46324
|
+
let exportRoot = null;
|
|
46325
|
+
if (isNodeOfType(node, "ExportDefaultDeclaration")) exportRoot = node.declaration;
|
|
46326
|
+
else if (isNodeOfType(node, "AssignmentExpression") && isModuleExportsAssignment(node)) exportRoot = node.right;
|
|
46327
|
+
if (!exportRoot) return;
|
|
46328
|
+
if (isFunctionLike$1(exportRoot) || isNodeOfType(exportRoot, "ObjectExpression")) return;
|
|
46329
|
+
walkAst(exportRoot, (exportNode) => {
|
|
46330
|
+
if (isFunctionLike$1(exportNode) || isNodeOfType(exportNode, "ObjectExpression")) return false;
|
|
46331
|
+
if (!isNodeOfType(exportNode, "Identifier")) return;
|
|
46332
|
+
const symbol = scopes.symbolFor(exportNode);
|
|
46333
|
+
if (symbol) exportedBindings.add(symbol);
|
|
46334
|
+
});
|
|
46335
|
+
});
|
|
46336
|
+
return exportedBindings;
|
|
46337
|
+
};
|
|
46338
|
+
const isExportedConfigProperty = (property, exportedBindings, scopes) => {
|
|
46339
|
+
let ancestor = property.parent;
|
|
46340
|
+
let didFindContainingObject = false;
|
|
46341
|
+
let didCrossNestedProperty = false;
|
|
46342
|
+
let didCrossFunctionBoundary = false;
|
|
46343
|
+
let containingFunction = null;
|
|
46344
|
+
let containingReturn = null;
|
|
46345
|
+
while (ancestor) {
|
|
46346
|
+
if (isNodeOfType(ancestor, "ObjectExpression") && !didFindContainingObject) didFindContainingObject = true;
|
|
46347
|
+
else if (didFindContainingObject && isNodeOfType(ancestor, "Property")) didCrossNestedProperty = true;
|
|
46348
|
+
if (!containingReturn && isNodeOfType(ancestor, "ReturnStatement")) containingReturn = ancestor;
|
|
46349
|
+
if (isFunctionLike$1(ancestor)) if (containingFunction) didCrossFunctionBoundary = true;
|
|
46350
|
+
else containingFunction = ancestor;
|
|
46351
|
+
if (isNodeOfType(ancestor, "ExportDefaultDeclaration") || isModuleExportsAssignment(ancestor)) return !didCrossNestedProperty && !didCrossFunctionBoundary && (!containingFunction || Boolean(containingReturn) || !isNodeOfType(containingFunction.body, "BlockStatement"));
|
|
46352
|
+
if (isNodeOfType(ancestor, "VariableDeclarator") && isNodeOfType(ancestor.id, "Identifier")) {
|
|
46353
|
+
const binding = scopes.symbolFor(ancestor.id);
|
|
46354
|
+
if (binding && exportedBindings.has(binding)) return !didCrossNestedProperty && !containingFunction;
|
|
46355
|
+
}
|
|
46356
|
+
ancestor = ancestor.parent;
|
|
46357
|
+
}
|
|
46358
|
+
return false;
|
|
46359
|
+
};
|
|
46360
|
+
const getExplicitConfigPaths = (packageDirectory, manifest) => {
|
|
46361
|
+
const configPaths = new Set(FAST_REFRESH_CONFIG_FILENAMES.map((configFilename) => path.join(packageDirectory, configFilename)));
|
|
46362
|
+
for (const script of Object.values(manifest.scripts ?? {})) {
|
|
46363
|
+
if (typeof script !== "string") continue;
|
|
46364
|
+
for (const match of script.matchAll(/(?:^|\s)--config(?:=|\s+)(?:"([^"]+)"|'([^']+)'|([^\s;&|]+))/g)) {
|
|
46365
|
+
const relativeConfigPath = match[1] ?? match[2] ?? match[3];
|
|
46366
|
+
if (!relativeConfigPath) continue;
|
|
46367
|
+
const configPath = path.resolve(packageDirectory, relativeConfigPath);
|
|
46368
|
+
const relativePath = path.relative(packageDirectory, configPath);
|
|
46369
|
+
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) continue;
|
|
46370
|
+
configPaths.add(configPath);
|
|
46371
|
+
}
|
|
46372
|
+
}
|
|
46373
|
+
return [...configPaths];
|
|
46374
|
+
};
|
|
46375
|
+
const getRegisteredIntegration = (packageDirectory, manifest) => {
|
|
46376
|
+
for (const configPath of getExplicitConfigPaths(packageDirectory, manifest)) {
|
|
46377
|
+
const program = parseSourceFile(configPath);
|
|
46378
|
+
if (!program) continue;
|
|
46379
|
+
const scopes = analyzeScopes(program);
|
|
46380
|
+
const integrationLocalBindings = getIntegrationLocalNames(program, scopes);
|
|
46381
|
+
if (integrationLocalBindings.size === 0) continue;
|
|
46382
|
+
const exportedBindings = getExportedBindings(program, scopes);
|
|
46383
|
+
const registeredIntegrations = [];
|
|
46384
|
+
walkAst(program, (node) => {
|
|
46385
|
+
if (!isNodeOfType(node, "Property")) return;
|
|
46386
|
+
if (!(isNodeOfType(node.key, "Identifier") && node.key.name === "plugins" || isNodeOfType(node.key, "Literal") && node.key.value === "plugins")) return;
|
|
46387
|
+
if (!isExportedConfigProperty(node, exportedBindings, scopes)) return;
|
|
46388
|
+
const inspectedBindings = /* @__PURE__ */ new Set();
|
|
46389
|
+
const inspectValue = (value) => {
|
|
46390
|
+
walkAst(value, (valueNode) => {
|
|
46391
|
+
if (isNodeOfType(valueNode, "Identifier")) {
|
|
46392
|
+
const symbol = scopes.symbolFor(valueNode);
|
|
46393
|
+
const initializer = symbol ? getDirectUnreassignedInitializer(symbol) : null;
|
|
46394
|
+
if (symbol && initializer && !inspectedBindings.has(symbol.id)) {
|
|
46395
|
+
inspectedBindings.add(symbol.id);
|
|
46396
|
+
inspectValue(initializer);
|
|
46397
|
+
}
|
|
46398
|
+
}
|
|
46399
|
+
if (!isNodeOfType(valueNode, "CallExpression") && !isNodeOfType(valueNode, "NewExpression")) return;
|
|
46400
|
+
if (!isNodeOfType(valueNode.callee, "Identifier")) return;
|
|
46401
|
+
const symbol = scopes.symbolFor(valueNode.callee);
|
|
46402
|
+
const integration = symbol ? integrationLocalBindings.get(symbol) : null;
|
|
46403
|
+
if (integration) registeredIntegrations.push(integration);
|
|
46404
|
+
});
|
|
46405
|
+
};
|
|
46406
|
+
inspectValue(node.value);
|
|
46407
|
+
return false;
|
|
46408
|
+
});
|
|
46409
|
+
const hasViteDevelopmentRuntime = hasViteDevelopmentCommand(manifest) || hasViteBrowserEntry(manifest, packageDirectory);
|
|
46410
|
+
const registeredIntegration = REGISTERED_INTEGRATION_RUNTIME_PRECEDENCE.flatMap((runtime) => registeredIntegrations.filter((integration) => integration.runtime === runtime && (!integration.requiresViteDevelopmentServer || hasViteDevelopmentRuntime)))[0];
|
|
46411
|
+
if (registeredIntegration) return {
|
|
46412
|
+
isActive: true,
|
|
46413
|
+
runtime: registeredIntegration.runtime
|
|
46414
|
+
};
|
|
46415
|
+
}
|
|
46416
|
+
return null;
|
|
46417
|
+
};
|
|
46418
|
+
const hasStorybookReactViteConfig = (packageDirectory) => {
|
|
46419
|
+
for (const configFilename of [
|
|
46420
|
+
"main.ts",
|
|
46421
|
+
"main.js",
|
|
46422
|
+
"main.mjs",
|
|
46423
|
+
"main.cjs"
|
|
46424
|
+
]) {
|
|
46425
|
+
const program = parseSourceFile(path.join(packageDirectory, ".storybook", configFilename));
|
|
46426
|
+
if (!program) continue;
|
|
46427
|
+
const scopes = analyzeScopes(program);
|
|
46428
|
+
const exportedBindings = getExportedBindings(program, scopes);
|
|
46429
|
+
let hasReactViteFramework = false;
|
|
46430
|
+
walkAst(program, (node) => {
|
|
46431
|
+
if (!isNodeOfType(node, "Property")) return;
|
|
46432
|
+
if (!(isNodeOfType(node.key, "Identifier") && node.key.name === "framework" || isNodeOfType(node.key, "Literal") && node.key.value === "framework")) return;
|
|
46433
|
+
if (!isExportedConfigProperty(node, exportedBindings, scopes)) return;
|
|
46434
|
+
walkAst(node.value, (valueNode) => {
|
|
46435
|
+
if (isNodeOfType(valueNode, "Literal") && valueNode.value === "@storybook/react-vite") {
|
|
46436
|
+
hasReactViteFramework = true;
|
|
46437
|
+
return false;
|
|
46438
|
+
}
|
|
46439
|
+
});
|
|
46440
|
+
if (hasReactViteFramework) return false;
|
|
46441
|
+
});
|
|
46442
|
+
if (hasReactViteFramework) return true;
|
|
46443
|
+
}
|
|
46444
|
+
return false;
|
|
46445
|
+
};
|
|
46446
|
+
const resolveDirectUnreassignedValue = (value, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
46447
|
+
const unwrappedValue = stripParenExpression(value);
|
|
46448
|
+
if (!isNodeOfType(unwrappedValue, "Identifier")) return unwrappedValue;
|
|
46449
|
+
const symbol = scopes.symbolFor(unwrappedValue);
|
|
46450
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return unwrappedValue;
|
|
46451
|
+
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
46452
|
+
if (!initializer) return unwrappedValue;
|
|
46453
|
+
visitedSymbolIds.add(symbol.id);
|
|
46454
|
+
return resolveDirectUnreassignedValue(initializer, scopes, visitedSymbolIds);
|
|
46455
|
+
};
|
|
46456
|
+
const isStaticPropertyNamed = (node, propertyName) => isNodeOfType(node, "Property") && (!node.computed && isNodeOfType(node.key, "Identifier") && node.key.name === propertyName || isNodeOfType(node.key, "Literal") && node.key.value === propertyName);
|
|
46457
|
+
const readLastStaticBooleanProperty = (value, propertyName, scopes) => {
|
|
46458
|
+
const resolvedValue = resolveDirectUnreassignedValue(value, scopes);
|
|
46459
|
+
if (!isNodeOfType(resolvedValue, "ObjectExpression")) return null;
|
|
46460
|
+
let propertyValue = null;
|
|
46461
|
+
for (const property of resolvedValue.properties) {
|
|
46462
|
+
if (isNodeOfType(property, "SpreadElement")) {
|
|
46463
|
+
propertyValue = null;
|
|
46464
|
+
continue;
|
|
46465
|
+
}
|
|
46466
|
+
if (!isStaticPropertyNamed(property, propertyName) || !isNodeOfType(property, "Property")) continue;
|
|
46467
|
+
propertyValue = readStaticBoolean(resolveDirectUnreassignedValue(property.value, scopes));
|
|
46468
|
+
}
|
|
46469
|
+
return propertyValue;
|
|
46470
|
+
};
|
|
46471
|
+
const isLastStaticPropertyInObject = (property, propertyName) => {
|
|
46472
|
+
if (!isNodeOfType(property.parent, "ObjectExpression")) return false;
|
|
46473
|
+
const propertyIndex = property.parent.properties.findIndex((objectProperty) => objectProperty === property);
|
|
46474
|
+
return property.parent.properties.slice(propertyIndex + 1).every((laterProperty) => {
|
|
46475
|
+
if (isNodeOfType(laterProperty, "SpreadElement")) return false;
|
|
46476
|
+
return !isStaticPropertyNamed(laterProperty, propertyName);
|
|
46477
|
+
});
|
|
46478
|
+
};
|
|
46479
|
+
const hasStorybookReactWebpackFastRefreshConfig = (packageDirectory) => {
|
|
46480
|
+
for (const configFilename of [
|
|
46481
|
+
"main.ts",
|
|
46482
|
+
"main.js",
|
|
46483
|
+
"main.mjs",
|
|
46484
|
+
"main.cjs"
|
|
46485
|
+
]) {
|
|
46486
|
+
const program = parseSourceFile(path.join(packageDirectory, ".storybook", configFilename));
|
|
46487
|
+
if (!program) continue;
|
|
46488
|
+
const scopes = analyzeScopes(program);
|
|
46489
|
+
const exportedBindings = getExportedBindings(program, scopes);
|
|
46490
|
+
let hasFastRefresh = false;
|
|
46491
|
+
walkAst(program, (node) => {
|
|
46492
|
+
if (!isStaticPropertyNamed(node, "reactOptions") || !isNodeOfType(node, "Property")) return;
|
|
46493
|
+
if (!isExportedConfigProperty(node, exportedBindings, scopes)) return;
|
|
46494
|
+
if (!isLastStaticPropertyInObject(node, "reactOptions")) return;
|
|
46495
|
+
if (readLastStaticBooleanProperty(node.value, "fastRefresh", scopes) === true) {
|
|
46496
|
+
hasFastRefresh = true;
|
|
46497
|
+
return false;
|
|
46498
|
+
}
|
|
46499
|
+
});
|
|
46500
|
+
if (hasFastRefresh) return true;
|
|
46501
|
+
}
|
|
46502
|
+
return false;
|
|
46503
|
+
};
|
|
46504
|
+
const hasStorybookReactViteIntegration = (packageDirectory, manifest) => hasOwnedDevelopmentCommand(manifest, /(?:^|\s)(?:storybook\s+dev|start-storybook)(?:\s|$)/) && hasStorybookReactViteConfig(packageDirectory);
|
|
46505
|
+
const hasStorybookReactWebpackFastRefreshIntegration = (packageDirectory, manifest) => {
|
|
46506
|
+
const developmentCommandPattern = /(?:^|\s)(?:storybook\s+dev|start-storybook)(?:\s|$)/;
|
|
46507
|
+
if (!hasOwnedDevelopmentCommand(manifest, developmentCommandPattern)) return false;
|
|
46508
|
+
return isVersionAtLeast(getOwnedDependencyVersion(manifest, "@storybook/react", developmentCommandPattern) ?? getOwnedDependencyVersion(manifest, "@storybook/react-webpack5", developmentCommandPattern), MINIMUM_FAST_REFRESH_VERSIONS.storybookReact) && hasStorybookReactWebpackFastRefreshConfig(packageDirectory);
|
|
46509
|
+
};
|
|
46510
|
+
const hasNxStorybookReactViteIntegration = (packageDirectory) => {
|
|
46511
|
+
if (!hasStorybookReactViteConfig(packageDirectory)) return false;
|
|
46512
|
+
try {
|
|
46513
|
+
const project = JSON.parse(fs.readFileSync(path.join(packageDirectory, "project.json"), "utf8"));
|
|
46514
|
+
if (typeof project !== "object" || project === null) return false;
|
|
46515
|
+
const targets = Reflect.get(project, "targets");
|
|
46516
|
+
if (typeof targets !== "object" || targets === null) return false;
|
|
46517
|
+
return Object.keys(targets).some((targetName) => targetName === "storybook:serve:dev");
|
|
46518
|
+
} catch {
|
|
46519
|
+
return false;
|
|
46520
|
+
}
|
|
46521
|
+
};
|
|
46522
|
+
const getLocalFastRefreshStatus = (packageDirectory, manifest) => {
|
|
46523
|
+
const cachedStatus = cachedLocalStatusByManifest.get(manifest);
|
|
46524
|
+
if (cachedStatus) return cachedStatus;
|
|
46525
|
+
const status = getBuiltInStatus(manifest) ?? (hasStorybookReactViteIntegration(packageDirectory, manifest) || hasNxStorybookReactViteIntegration(packageDirectory) || hasStorybookReactWebpackFastRefreshIntegration(packageDirectory, manifest) ? {
|
|
46526
|
+
isActive: true,
|
|
46527
|
+
runtime: "generic"
|
|
46528
|
+
} : null) ?? getRegisteredIntegration(packageDirectory, manifest) ?? INACTIVE_STATUS;
|
|
46529
|
+
cachedLocalStatusByManifest.set(manifest, status);
|
|
46530
|
+
return status;
|
|
46531
|
+
};
|
|
46532
|
+
const isWorkspaceRoot = (directory, manifest) => {
|
|
46533
|
+
if (manifest?.workspaces !== void 0) return true;
|
|
46534
|
+
return [
|
|
46535
|
+
"pnpm-workspace.yaml",
|
|
46536
|
+
"pnpm-workspace.yml",
|
|
46537
|
+
"nx.json"
|
|
46538
|
+
].some((filename) => fs.existsSync(path.join(directory, filename)));
|
|
46539
|
+
};
|
|
46540
|
+
const findWorkspaceRoot = (packageDirectory) => {
|
|
46541
|
+
let currentDirectory = packageDirectory;
|
|
46542
|
+
let workspaceRoot = null;
|
|
46543
|
+
while (true) {
|
|
46544
|
+
const manifest = readPackageManifest(currentDirectory);
|
|
46545
|
+
if (isWorkspaceRoot(currentDirectory, manifest)) workspaceRoot = currentDirectory;
|
|
46546
|
+
const parentDirectory = path.dirname(currentDirectory);
|
|
46547
|
+
if (parentDirectory === currentDirectory) return workspaceRoot;
|
|
46548
|
+
currentDirectory = parentDirectory;
|
|
46549
|
+
}
|
|
46550
|
+
};
|
|
46551
|
+
const collectWorkspacePackages = (workspaceRoot) => {
|
|
46552
|
+
const packages = [];
|
|
46553
|
+
const pendingDirectories = [workspaceRoot];
|
|
46554
|
+
while (pendingDirectories.length > 0) {
|
|
46555
|
+
const directory = pendingDirectories.pop();
|
|
46556
|
+
if (!directory) continue;
|
|
46557
|
+
const manifest = readPackageManifest(directory);
|
|
46558
|
+
if (manifest) packages.push({
|
|
46559
|
+
directory,
|
|
46560
|
+
manifest,
|
|
46561
|
+
status: getLocalFastRefreshStatus(directory, manifest)
|
|
46562
|
+
});
|
|
46563
|
+
let entries;
|
|
46564
|
+
try {
|
|
46565
|
+
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
46566
|
+
} catch {
|
|
46567
|
+
continue;
|
|
46568
|
+
}
|
|
46569
|
+
for (const entry of entries) {
|
|
46570
|
+
if (!entry.isDirectory()) continue;
|
|
46571
|
+
if (entry.name.startsWith(".") || WORKSPACE_IGNORED_DIRECTORY_NAMES.has(entry.name)) continue;
|
|
46572
|
+
pendingDirectories.push(path.join(directory, entry.name));
|
|
46573
|
+
}
|
|
46574
|
+
}
|
|
46575
|
+
return packages;
|
|
46576
|
+
};
|
|
46577
|
+
const isPropertyNamed = (node, name) => isNodeOfType(node, "Property") && (isNodeOfType(node.key, "Identifier") && node.key.name === name || isNodeOfType(node.key, "Literal") && node.key.value === name);
|
|
46578
|
+
const normalizeAliasRoot = (configDirectory, aliasPath) => {
|
|
46579
|
+
if (!aliasPath.startsWith(".") && !path.isAbsolute(aliasPath)) return null;
|
|
46580
|
+
const withoutPlaceholder = aliasPath.replace(/(?:\/)?(?:\*|\$\d+).*$/, "");
|
|
46581
|
+
return path.resolve(configDirectory, withoutPlaceholder);
|
|
46582
|
+
};
|
|
46583
|
+
const collectAliasRootsFromValue = (value, configDirectory, aliasRoots) => {
|
|
46584
|
+
walkAst(value, (node) => {
|
|
46585
|
+
if (!isPropertyNamed(node, "alias")) return;
|
|
46586
|
+
if (!isNodeOfType(node, "Property")) return;
|
|
46587
|
+
walkAst(node.value, (aliasNode) => {
|
|
46588
|
+
if (!isNodeOfType(aliasNode, "Literal") || typeof aliasNode.value !== "string") return;
|
|
46589
|
+
const aliasRoot = normalizeAliasRoot(configDirectory, aliasNode.value);
|
|
46590
|
+
if (aliasRoot) aliasRoots.add(aliasRoot);
|
|
46591
|
+
});
|
|
46592
|
+
return false;
|
|
46593
|
+
});
|
|
46594
|
+
};
|
|
46595
|
+
const getActivePackageAliasRoots = (packageDirectory, manifest) => {
|
|
46596
|
+
const aliasRoots = /* @__PURE__ */ new Set();
|
|
46597
|
+
const configPaths = [...getExplicitConfigPaths(packageDirectory, manifest), ...[
|
|
46598
|
+
"main.ts",
|
|
46599
|
+
"main.js",
|
|
46600
|
+
"main.mjs",
|
|
46601
|
+
"main.cjs"
|
|
46602
|
+
].map((filename) => path.join(packageDirectory, ".storybook", filename))];
|
|
46603
|
+
for (const configPath of configPaths) {
|
|
46604
|
+
const program = parseSourceFile(configPath);
|
|
46605
|
+
if (!program) continue;
|
|
46606
|
+
const scopes = analyzeScopes(program);
|
|
46607
|
+
const exportedBindings = getExportedBindings(program, scopes);
|
|
46608
|
+
walkAst(program, (node) => {
|
|
46609
|
+
if (!isPropertyNamed(node, "resolve") && !isPropertyNamed(node, "viteFinal")) return;
|
|
46610
|
+
if (!isNodeOfType(node, "Property")) return;
|
|
46611
|
+
if (!isExportedConfigProperty(node, exportedBindings, scopes)) return;
|
|
46612
|
+
collectAliasRootsFromValue(node.value, path.dirname(configPath), aliasRoots);
|
|
46613
|
+
return false;
|
|
46614
|
+
});
|
|
46615
|
+
}
|
|
46616
|
+
return aliasRoots;
|
|
46617
|
+
};
|
|
46618
|
+
const collectStringValues = (value, values) => {
|
|
46619
|
+
if (typeof value === "string") {
|
|
46620
|
+
values.push(value);
|
|
46621
|
+
return;
|
|
46622
|
+
}
|
|
46623
|
+
if (Array.isArray(value)) {
|
|
46624
|
+
for (const entry of value) collectStringValues(entry, values);
|
|
46625
|
+
return;
|
|
46626
|
+
}
|
|
46627
|
+
if (typeof value !== "object" || value === null) return;
|
|
46628
|
+
for (const entry of Object.values(value)) collectStringValues(entry, values);
|
|
46629
|
+
};
|
|
46630
|
+
const hasSourceRuntimeEntry = (manifest) => {
|
|
46631
|
+
const entryValues = [];
|
|
46632
|
+
collectStringValues(manifest.exports, entryValues);
|
|
46633
|
+
collectStringValues(manifest.main, entryValues);
|
|
46634
|
+
collectStringValues(manifest.module, entryValues);
|
|
46635
|
+
return entryValues.some((entry) => /^\.\/?src(?:\/|\.|$)/.test(entry));
|
|
46636
|
+
};
|
|
46637
|
+
const getWorkspaceDependencyVersion = (manifest, dependencyName) => manifest.dependencies?.[dependencyName] ?? manifest.optionalDependencies?.[dependencyName];
|
|
46638
|
+
const buildWorkspaceFastRefreshIndex = (workspaceRoot, rootManifest) => {
|
|
46639
|
+
const cached = cachedWorkspaceIndexByManifest.get(rootManifest);
|
|
46640
|
+
if (cached) return cached;
|
|
46641
|
+
const workspacePackages = collectWorkspacePackages(workspaceRoot);
|
|
46642
|
+
const activePackages = workspacePackages.filter((workspacePackage) => workspacePackage.status.isActive);
|
|
46643
|
+
const aliasOwners = [];
|
|
46644
|
+
for (const activePackage of activePackages) for (const rootDirectory of getActivePackageAliasRoots(activePackage.directory, activePackage.manifest)) aliasOwners.push({
|
|
46645
|
+
rootDirectory,
|
|
46646
|
+
status: activePackage.status
|
|
46647
|
+
});
|
|
46648
|
+
const sourceEntryOwners = /* @__PURE__ */ new Map();
|
|
46649
|
+
for (const producerPackage of workspacePackages) {
|
|
46650
|
+
const packageName = producerPackage.manifest.name;
|
|
46651
|
+
if (typeof packageName !== "string" || !hasSourceRuntimeEntry(producerPackage.manifest)) continue;
|
|
46652
|
+
const owner = activePackages.find((activePackage) => {
|
|
46653
|
+
const dependencyVersion = getWorkspaceDependencyVersion(activePackage.manifest, packageName);
|
|
46654
|
+
return typeof dependencyVersion === "string" && dependencyVersion.startsWith("workspace:");
|
|
46655
|
+
});
|
|
46656
|
+
if (owner) sourceEntryOwners.set(producerPackage.directory, owner.status);
|
|
46657
|
+
}
|
|
46658
|
+
const index = {
|
|
46659
|
+
aliasOwners,
|
|
46660
|
+
sourceEntryOwners
|
|
46661
|
+
};
|
|
46662
|
+
cachedWorkspaceIndexByManifest.set(rootManifest, index);
|
|
46663
|
+
return index;
|
|
46664
|
+
};
|
|
46665
|
+
const isPathInside = (filePath, directory) => {
|
|
46666
|
+
const relativePath = path.relative(directory, filePath);
|
|
46667
|
+
return relativePath === "" || !relativePath.startsWith(`..${path.sep}`) && relativePath !== ".." && !path.isAbsolute(relativePath);
|
|
46668
|
+
};
|
|
46669
|
+
const getWorkspaceOwnedStatus = (filename, packageDirectory) => {
|
|
46670
|
+
const workspaceRoot = findWorkspaceRoot(packageDirectory);
|
|
46671
|
+
if (!workspaceRoot) return null;
|
|
46672
|
+
const rootManifest = readPackageManifest(workspaceRoot);
|
|
46673
|
+
if (!rootManifest) return null;
|
|
46674
|
+
const index = buildWorkspaceFastRefreshIndex(workspaceRoot, rootManifest);
|
|
46675
|
+
const aliasOwner = index.aliasOwners.find((owner) => isPathInside(filename, owner.rootDirectory));
|
|
46676
|
+
if (aliasOwner) return aliasOwner.status;
|
|
46677
|
+
for (const [producerDirectory, status] of index.sourceEntryOwners) if (isPathInside(filename, producerDirectory)) return status;
|
|
46678
|
+
return null;
|
|
46679
|
+
};
|
|
46680
|
+
const resolveFastRefreshFileStatus = (filename) => {
|
|
46681
|
+
const manifest = readNearestPackageManifest(filename);
|
|
46682
|
+
if (!manifest) return INACTIVE_STATUS;
|
|
46683
|
+
const packageDirectory = findNearestPackageDirectory(filename);
|
|
46684
|
+
if (!packageDirectory) return INACTIVE_STATUS;
|
|
46685
|
+
const localStatus = getLocalFastRefreshStatus(packageDirectory, manifest);
|
|
46686
|
+
if (localStatus.isActive) return localStatus;
|
|
46687
|
+
return getWorkspaceOwnedStatus(filename, packageDirectory) ?? INACTIVE_STATUS;
|
|
46688
|
+
};
|
|
46689
|
+
const getConfiguredFallbackStatus = (context) => {
|
|
46690
|
+
const framework = getReactDoctorStringSetting(context.settings, "framework");
|
|
46691
|
+
return {
|
|
46692
|
+
isActive: true,
|
|
46693
|
+
runtime: framework === "nextjs" ? "next" : framework === "expo" || framework === "react-native" ? "expo" : framework === "remix" ? "remix" : framework === "tanstack-start" ? "tanstack" : "generic"
|
|
46694
|
+
};
|
|
46695
|
+
};
|
|
46696
|
+
const getFastRefreshFileStatus = (context) => {
|
|
46697
|
+
let filename = context.filename;
|
|
46698
|
+
if (!filename) return getConfiguredFallbackStatus(context);
|
|
46699
|
+
if (!path.isAbsolute(filename)) {
|
|
46700
|
+
const absoluteFilename = path.resolve(filename);
|
|
46701
|
+
if (!fs.existsSync(absoluteFilename)) return getConfiguredFallbackStatus(context);
|
|
46702
|
+
filename = absoluteFilename;
|
|
46703
|
+
}
|
|
46704
|
+
if (!readNearestPackageManifest(filename)) return INACTIVE_STATUS;
|
|
46705
|
+
return resolveFastRefreshFileStatus(filename);
|
|
45760
46706
|
};
|
|
45761
46707
|
//#endregion
|
|
45762
46708
|
//#region src/plugin/rules/react-builtins/only-export-components-tables.ts
|
|
@@ -45768,6 +46714,7 @@ const NOT_REACT_COMPONENT_EXPRESSION_TYPES = new Set([
|
|
|
45768
46714
|
"ConditionalExpression",
|
|
45769
46715
|
"Literal",
|
|
45770
46716
|
"LogicalExpression",
|
|
46717
|
+
"NewExpression",
|
|
45771
46718
|
"ObjectExpression",
|
|
45772
46719
|
"TemplateLiteral",
|
|
45773
46720
|
"ThisExpression",
|
|
@@ -45786,124 +46733,25 @@ const NON_FAST_REFRESH_PATH_SEGMENTS = [
|
|
|
45786
46733
|
"/cypress/",
|
|
45787
46734
|
"/.storybook/",
|
|
45788
46735
|
"/stories/",
|
|
45789
|
-
"/__stories__/"
|
|
45790
|
-
"/playground/",
|
|
45791
|
-
"/playgrounds/",
|
|
45792
|
-
"/examples/",
|
|
45793
|
-
"/example/",
|
|
45794
|
-
"/demo/",
|
|
45795
|
-
"/demos/",
|
|
45796
|
-
"/sandbox/",
|
|
45797
|
-
"/sandboxes/"
|
|
46736
|
+
"/__stories__/"
|
|
45798
46737
|
];
|
|
45799
|
-
const
|
|
45800
|
-
"main.tsx",
|
|
45801
|
-
"main.jsx",
|
|
45802
|
-
"main.js",
|
|
45803
|
-
"index.tsx",
|
|
45804
|
-
"index.jsx",
|
|
45805
|
-
"entry.tsx",
|
|
45806
|
-
"entry.jsx",
|
|
45807
|
-
"bootstrap.tsx",
|
|
45808
|
-
"bootstrap.jsx",
|
|
45809
|
-
"client.tsx",
|
|
45810
|
-
"client.jsx",
|
|
45811
|
-
"server.tsx",
|
|
45812
|
-
"server.jsx",
|
|
45813
|
-
"app.tsx",
|
|
45814
|
-
"app.jsx",
|
|
45815
|
-
"App.tsx",
|
|
45816
|
-
"App.jsx"
|
|
45817
|
-
]);
|
|
45818
|
-
const UTILITY_FILE_BASENAMES = new Set([
|
|
45819
|
-
"utils.tsx",
|
|
45820
|
-
"utils.jsx",
|
|
45821
|
-
"util.tsx",
|
|
45822
|
-
"util.jsx",
|
|
45823
|
-
"helpers.tsx",
|
|
45824
|
-
"helpers.jsx",
|
|
45825
|
-
"helper.tsx",
|
|
45826
|
-
"helper.jsx",
|
|
45827
|
-
"shared.tsx",
|
|
45828
|
-
"shared.jsx",
|
|
45829
|
-
"common.tsx",
|
|
45830
|
-
"common.jsx",
|
|
45831
|
-
"lib.tsx",
|
|
45832
|
-
"lib.jsx",
|
|
45833
|
-
"nodeTypes.tsx",
|
|
45834
|
-
"nodeTypes.jsx",
|
|
45835
|
-
"node-types.tsx",
|
|
45836
|
-
"node-types.jsx",
|
|
45837
|
-
"edgeTypes.tsx",
|
|
45838
|
-
"edgeTypes.jsx",
|
|
45839
|
-
"edge-types.tsx",
|
|
45840
|
-
"edge-types.jsx",
|
|
45841
|
-
"cellTypes.tsx",
|
|
45842
|
-
"cellTypes.jsx",
|
|
45843
|
-
"columnTypes.tsx",
|
|
45844
|
-
"columnTypes.jsx",
|
|
45845
|
-
"columnDefs.tsx",
|
|
45846
|
-
"columnDefs.jsx",
|
|
45847
|
-
"columnRenderers.tsx",
|
|
45848
|
-
"columnRenderers.jsx",
|
|
45849
|
-
"columns.tsx",
|
|
45850
|
-
"columns.jsx",
|
|
45851
|
-
"mappings.tsx",
|
|
45852
|
-
"mappings.jsx",
|
|
45853
|
-
"mapping.tsx",
|
|
45854
|
-
"mapping.jsx",
|
|
45855
|
-
"lookups.tsx",
|
|
45856
|
-
"lookups.jsx",
|
|
45857
|
-
"lookup.tsx",
|
|
45858
|
-
"lookup.jsx",
|
|
45859
|
-
"registry.tsx",
|
|
45860
|
-
"registry.jsx",
|
|
45861
|
-
"toast.tsx",
|
|
45862
|
-
"toast.jsx",
|
|
45863
|
-
"toaster.tsx",
|
|
45864
|
-
"toaster.jsx",
|
|
45865
|
-
"theme.tsx",
|
|
45866
|
-
"theme.jsx",
|
|
45867
|
-
"tokens.tsx",
|
|
45868
|
-
"tokens.jsx",
|
|
45869
|
-
"palette.tsx",
|
|
45870
|
-
"palette.jsx",
|
|
45871
|
-
"colors.tsx",
|
|
45872
|
-
"colors.jsx",
|
|
45873
|
-
"colours.tsx",
|
|
45874
|
-
"colours.jsx",
|
|
45875
|
-
"constants.tsx",
|
|
45876
|
-
"constants.jsx",
|
|
45877
|
-
"enums.tsx",
|
|
45878
|
-
"enums.jsx",
|
|
45879
|
-
"types.tsx",
|
|
45880
|
-
"types.jsx",
|
|
45881
|
-
"schemas.tsx",
|
|
45882
|
-
"schemas.jsx",
|
|
45883
|
-
"schema.tsx",
|
|
45884
|
-
"schema.jsx",
|
|
45885
|
-
"definitions.tsx",
|
|
45886
|
-
"definitions.jsx",
|
|
45887
|
-
"config.tsx",
|
|
45888
|
-
"config.jsx",
|
|
45889
|
-
"defaults.tsx",
|
|
45890
|
-
"defaults.jsx"
|
|
45891
|
-
]);
|
|
45892
|
-
const ROUTE_FACTORY_CALLEE_NAMES = new Set([
|
|
46738
|
+
const TANSTACK_ROUTE_FACTORY_CALLEE_NAMES = new Set([
|
|
45893
46739
|
...TANSTACK_ROUTE_CREATION_FUNCTIONS,
|
|
45894
46740
|
"createLazyFileRoute",
|
|
45895
46741
|
"createLazyRoute",
|
|
45896
46742
|
"createAPIFileRoute",
|
|
45897
46743
|
"createServerFileRoute",
|
|
45898
46744
|
"createServerRootRoute",
|
|
45899
|
-
"createServerRoute"
|
|
46745
|
+
"createServerRoute"
|
|
46746
|
+
]);
|
|
46747
|
+
const REACT_ROUTER_FACTORY_CALLEE_NAMES = new Set([
|
|
45900
46748
|
"createBrowserRouter",
|
|
45901
46749
|
"createHashRouter",
|
|
45902
46750
|
"createMemoryRouter",
|
|
45903
46751
|
"createStaticRouter",
|
|
45904
46752
|
"createRouter"
|
|
45905
46753
|
]);
|
|
45906
|
-
const
|
|
46754
|
+
const REACT_ROUTER_ALLOWED_EXPORT_NAMES = new Set([
|
|
45907
46755
|
"loader",
|
|
45908
46756
|
"clientLoader",
|
|
45909
46757
|
"action",
|
|
@@ -45914,7 +46762,9 @@ const ROUTE_MODULE_ALLOWED_EXPORT_NAMES = new Set([
|
|
|
45914
46762
|
"handle",
|
|
45915
46763
|
"shouldRevalidate",
|
|
45916
46764
|
"middleware",
|
|
45917
|
-
"unstable_middleware"
|
|
46765
|
+
"unstable_middleware"
|
|
46766
|
+
]);
|
|
46767
|
+
const NEXT_ALLOWED_EXPORT_NAMES = new Set([
|
|
45918
46768
|
"getServerSideProps",
|
|
45919
46769
|
"getStaticProps",
|
|
45920
46770
|
"getStaticPaths",
|
|
@@ -45934,9 +46784,9 @@ const ROUTE_MODULE_ALLOWED_EXPORT_NAMES = new Set([
|
|
|
45934
46784
|
"runtime",
|
|
45935
46785
|
"preferredRegion",
|
|
45936
46786
|
"maxDuration",
|
|
45937
|
-
"experimental_ppr"
|
|
45938
|
-
"unstable_settings"
|
|
46787
|
+
"experimental_ppr"
|
|
45939
46788
|
]);
|
|
46789
|
+
const EXPO_ALLOWED_EXPORT_NAMES = new Set(["unstable_settings"]);
|
|
45940
46790
|
//#endregion
|
|
45941
46791
|
//#region src/plugin/rules/react-builtins/only-export-components.ts
|
|
45942
46792
|
const NAMED_EXPORT_MESSAGE = "This file exports non-components, so Fast Refresh can't safely preserve component state.";
|
|
@@ -45949,6 +46799,8 @@ const DEFAULT_REACT_HOCS = [
|
|
|
45949
46799
|
"forwardRef",
|
|
45950
46800
|
"lazy"
|
|
45951
46801
|
];
|
|
46802
|
+
const EMPTY_NAME_SET = /* @__PURE__ */ new Set();
|
|
46803
|
+
const TEST_SUPPORT_FILE_PATTERN = /(?:^|\/)(?:test|spec)(?:[-_.]?(?:utils?|helpers?|setup|fixtures?))?\.(?:jsx?|tsx?)$/i;
|
|
45952
46804
|
const resolveSettings$6 = (settings) => {
|
|
45953
46805
|
const reactDoctor = settings?.["react-doctor"];
|
|
45954
46806
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.onlyExportComponents ?? {} : {};
|
|
@@ -45972,13 +46824,12 @@ const isReactCreateContext = (initializer) => {
|
|
|
45972
46824
|
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "createContext") return true;
|
|
45973
46825
|
return false;
|
|
45974
46826
|
};
|
|
45975
|
-
const
|
|
45976
|
-
const isRouteFactoryCall = (expression) => {
|
|
46827
|
+
const isRouteFactoryCall = (expression, bindings) => {
|
|
45977
46828
|
let currentCall = expression;
|
|
45978
46829
|
while (isNodeOfType(currentCall, "CallExpression")) {
|
|
45979
46830
|
const callee = currentCall.callee;
|
|
45980
|
-
if (isNodeOfType(callee, "Identifier") &&
|
|
45981
|
-
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") &&
|
|
46831
|
+
if (isNodeOfType(callee, "Identifier") && bindings.localNames.has(callee.name)) return true;
|
|
46832
|
+
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && bindings.namespaceNames.has(callee.object.name) && isNodeOfType(callee.property, "Identifier") && bindings.memberNames.has(callee.property.name)) return true;
|
|
45982
46833
|
if (!isNodeOfType(callee, "CallExpression")) return false;
|
|
45983
46834
|
currentCall = callee;
|
|
45984
46835
|
}
|
|
@@ -46006,10 +46857,11 @@ const isHocCallee = (callee, state) => {
|
|
|
46006
46857
|
const canBeReactFunctionComponent = (initializer, state) => {
|
|
46007
46858
|
if (!initializer) return false;
|
|
46008
46859
|
const expression = skipTsExpression(initializer);
|
|
46009
|
-
if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression")) return
|
|
46860
|
+
if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression")) return functionHasReactRenderSemantics(expression, state);
|
|
46010
46861
|
if (isNodeOfType(expression, "CallExpression")) return isHocCallee(expression.callee, state);
|
|
46011
46862
|
return false;
|
|
46012
46863
|
};
|
|
46864
|
+
const functionHasReactRenderSemantics = (functionNode, state) => functionContainsReactRenderOutput(functionNode, state.scopes, state.controlFlow) || functionHasReactElementReturnType(functionNode) || functionReturnsOnlyNull(functionNode);
|
|
46013
46865
|
const isReactComponentInitializer = (expression, state) => {
|
|
46014
46866
|
const stripped = skipTsExpression(expression);
|
|
46015
46867
|
if (isNodeOfType(stripped, "ArrowFunctionExpression")) return true;
|
|
@@ -46018,6 +46870,60 @@ const isReactComponentInitializer = (expression, state) => {
|
|
|
46018
46870
|
if (isNodeOfType(stripped, "CallExpression") && isHocCallee(stripped.callee, state) && stripped.arguments.length > 0) return true;
|
|
46019
46871
|
return false;
|
|
46020
46872
|
};
|
|
46873
|
+
const isNextDynamicCall = (expression, nextDynamicImportSymbolIds, scopes) => {
|
|
46874
|
+
const stripped = skipTsExpression(expression);
|
|
46875
|
+
if (!isNodeOfType(stripped, "CallExpression")) return false;
|
|
46876
|
+
const callee = skipTsExpression(stripped.callee);
|
|
46877
|
+
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
46878
|
+
const symbol = scopes.symbolFor(callee);
|
|
46879
|
+
return symbol !== null && nextDynamicImportSymbolIds.has(symbol.id);
|
|
46880
|
+
};
|
|
46881
|
+
const functionReturnsNextDynamicComponent = (expression, nextDynamicImportSymbolIds, scopes) => {
|
|
46882
|
+
const stripped = skipTsExpression(expression);
|
|
46883
|
+
if (!isNodeOfType(stripped, "ArrowFunctionExpression") && !isNodeOfType(stripped, "FunctionExpression") && !isNodeOfType(stripped, "FunctionDeclaration")) return false;
|
|
46884
|
+
const body = stripped.body;
|
|
46885
|
+
if (!isNodeOfType(body, "BlockStatement")) return isNextDynamicCall(body, nextDynamicImportSymbolIds, scopes);
|
|
46886
|
+
if (body.body.length !== 1) return false;
|
|
46887
|
+
const statement = body.body[0];
|
|
46888
|
+
return Boolean(statement) && isNodeOfType(statement, "ReturnStatement") && Boolean(statement.argument) && isNextDynamicCall(statement.argument, nextDynamicImportSymbolIds, scopes);
|
|
46889
|
+
};
|
|
46890
|
+
const isComponentFactoryCall = (expression, state) => {
|
|
46891
|
+
const stripped = skipTsExpression(expression);
|
|
46892
|
+
if (!isNodeOfType(stripped, "CallExpression")) return false;
|
|
46893
|
+
const callee = skipTsExpression(stripped.callee);
|
|
46894
|
+
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
46895
|
+
const symbol = state.scopes.symbolFor(callee);
|
|
46896
|
+
return symbol !== null && state.componentFactorySymbolIds.has(symbol.id);
|
|
46897
|
+
};
|
|
46898
|
+
const isProvenComponentValue = (expression, state, inspectedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
46899
|
+
const stripped = skipTsExpression(expression);
|
|
46900
|
+
if (isNodeOfType(stripped, "Identifier")) {
|
|
46901
|
+
const symbol = state.scopes.symbolFor(stripped);
|
|
46902
|
+
if (!symbol) return false;
|
|
46903
|
+
if (state.localComponentNames.has(stripped.name)) return symbol.references.every((reference) => reference.flag === "read");
|
|
46904
|
+
if (isReactComponentName(stripped.name) && symbol.kind === "import") return true;
|
|
46905
|
+
if (inspectedSymbolIds.has(symbol.id)) return false;
|
|
46906
|
+
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
46907
|
+
if (!initializer) return false;
|
|
46908
|
+
const strippedInitializer = skipTsExpression(initializer);
|
|
46909
|
+
if (isNodeOfType(strippedInitializer, "ArrowFunctionExpression") || isNodeOfType(strippedInitializer, "FunctionExpression")) return false;
|
|
46910
|
+
return isProvenComponentValue(initializer, state, new Set([...inspectedSymbolIds, symbol.id]));
|
|
46911
|
+
}
|
|
46912
|
+
if (isNodeOfType(stripped, "MemberExpression") && !stripped.computed && isNodeOfType(stripped.object, "Identifier") && isNodeOfType(stripped.property, "Identifier") && isReactComponentName(stripped.property.name)) {
|
|
46913
|
+
const objectSymbol = state.scopes.symbolFor(stripped.object);
|
|
46914
|
+
return objectSymbol !== null && state.importSymbolIds.has(objectSymbol.id);
|
|
46915
|
+
}
|
|
46916
|
+
if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "FunctionExpression")) return functionHasReactRenderSemantics(stripped, state);
|
|
46917
|
+
if (!isNodeOfType(stripped, "CallExpression")) return false;
|
|
46918
|
+
if (!isHocCallee(stripped.callee, state)) return false;
|
|
46919
|
+
return stripped.arguments.some((argument) => isProvenComponentValue(argument, state));
|
|
46920
|
+
};
|
|
46921
|
+
const isDirectRefreshWrapperCall = (call, state) => {
|
|
46922
|
+
const callee = skipTsExpression(call.callee);
|
|
46923
|
+
if (isHocCallee(callee, state)) return call.arguments.length > 0;
|
|
46924
|
+
if (!isNodeOfType(callee, "Identifier") && !isNodeOfType(callee, "MemberExpression")) return false;
|
|
46925
|
+
return call.arguments.some((argument) => isProvenComponentValue(argument, state));
|
|
46926
|
+
};
|
|
46021
46927
|
const objectExpressionBundlesComponents = (objectExpression, state) => {
|
|
46022
46928
|
for (const property of objectExpression.properties ?? []) {
|
|
46023
46929
|
if (!isNodeOfType(property, "Property")) continue;
|
|
@@ -46027,20 +46933,22 @@ const objectExpressionBundlesComponents = (objectExpression, state) => {
|
|
|
46027
46933
|
continue;
|
|
46028
46934
|
}
|
|
46029
46935
|
if (!(!property.computed && isNodeOfType(property.key, "Identifier") && isReactComponentName(property.key.name))) continue;
|
|
46030
|
-
if ((isNodeOfType(value, "ArrowFunctionExpression") || isNodeOfType(value, "FunctionExpression")) &&
|
|
46936
|
+
if ((isNodeOfType(value, "ArrowFunctionExpression") || isNodeOfType(value, "FunctionExpression")) && functionHasReactRenderSemantics(value, state)) return true;
|
|
46031
46937
|
if (isNodeOfType(value, "CallExpression") && isHocCallee(value.callee, state) && value.arguments.length > 0) return true;
|
|
46032
46938
|
}
|
|
46033
46939
|
return false;
|
|
46034
46940
|
};
|
|
46035
46941
|
const classifyExport = (name, reportNode, isFunction, initializer, state) => {
|
|
46942
|
+
if (isNodeOfType(reportNode, "Identifier") && isProvenComponentValue(reportNode, state)) return { kind: "react-component" };
|
|
46036
46943
|
if (initializer) {
|
|
46037
46944
|
const expression = skipTsExpression(initializer);
|
|
46038
|
-
if (
|
|
46945
|
+
if (isNodeOfType(expression, "CallExpression") && isReactComponentName(name) && isComponentFactoryCall(expression, state)) return { kind: "react-component" };
|
|
46946
|
+
if (isRouteFactoryCall(expression, state.routeFactoryBindings)) return { kind: "react-component" };
|
|
46039
46947
|
if (isNodeOfType(expression, "CallExpression") && isHocCallee(expression.callee, state) && expression.arguments.length > 0 && isReactComponentName(name)) return { kind: "react-component" };
|
|
46040
46948
|
if (isNodeOfType(expression, "ConditionalExpression") && isReactComponentName(name) && isReactComponentInitializer(expression.consequent, state) && isReactComponentInitializer(expression.alternate, state)) return { kind: "react-component" };
|
|
46041
46949
|
}
|
|
46042
46950
|
if (state.allowExportNames.has(name)) return { kind: "allowed" };
|
|
46043
|
-
if (
|
|
46951
|
+
if (state.allowedRouteExportNames.has(name)) return { kind: "allowed" };
|
|
46044
46952
|
if (/^use[A-Z]/.test(name)) return { kind: "allowed" };
|
|
46045
46953
|
if (state.allowConstantExport && initializer) {
|
|
46046
46954
|
const expression = skipTsExpression(initializer);
|
|
@@ -46066,42 +46974,33 @@ const classifyExport = (name, reportNode, isFunction, initializer, state) => {
|
|
|
46066
46974
|
kind: "namespace-object",
|
|
46067
46975
|
reportNode
|
|
46068
46976
|
};
|
|
46977
|
+
if (isNodeOfType(stripped, "MemberExpression")) return isProvenComponentValue(stripped, state) ? { kind: "react-component" } : {
|
|
46978
|
+
kind: "non-component",
|
|
46979
|
+
reportNode
|
|
46980
|
+
};
|
|
46981
|
+
if (isNodeOfType(stripped, "Identifier")) return isProvenComponentValue(stripped, state) ? { kind: "react-component" } : {
|
|
46982
|
+
kind: "non-component",
|
|
46983
|
+
reportNode
|
|
46984
|
+
};
|
|
46069
46985
|
if (NOT_REACT_COMPONENT_EXPRESSION_TYPES.has(stripped.type)) return {
|
|
46070
46986
|
kind: "non-component",
|
|
46071
46987
|
reportNode
|
|
46072
46988
|
};
|
|
46989
|
+
if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "FunctionExpression")) return {
|
|
46990
|
+
kind: "non-component",
|
|
46991
|
+
reportNode
|
|
46992
|
+
};
|
|
46073
46993
|
}
|
|
46074
46994
|
return isReactComponentName(name) ? { kind: "react-component" } : {
|
|
46075
46995
|
kind: "non-component",
|
|
46076
46996
|
reportNode
|
|
46077
46997
|
};
|
|
46078
46998
|
};
|
|
46079
|
-
const isEntryPointFile = (filename) => {
|
|
46080
|
-
const lastSlash = Math.max(filename.lastIndexOf("/"), filename.lastIndexOf("\\"));
|
|
46081
|
-
const basename = lastSlash === -1 ? filename : filename.slice(lastSlash + 1);
|
|
46082
|
-
return ENTRY_POINT_BASENAMES.has(basename);
|
|
46083
|
-
};
|
|
46084
|
-
const ASSET_FILE_BASENAME_PATTERN = /^([A-Za-z][\w-]*[-._])?(icons?|svgs?|svg[-_]?sprites?|sprites?|emojis?|flags?|logos?|lockups?|illustrations?|glyphs?|stickers?|emotes?|avatars?|backgrounds?|patterns?|assets?|gradients?|countryVectors?|paymentVectors?|brandVectors?|brandLogos?)\.(t|j)sx?$/;
|
|
46085
|
-
const UTILITY_BASENAME_SUFFIX_PATTERN = /^[A-Za-z][\w-]*(Utils|Util|Helpers|Helper|Shared|Constants|Constant|Types|Type|Mappings|Mapping|Lookups|Lookup|Registry|Renderers|Renderer|NodeTypes|EdgeTypes|CellTypes|ColumnDefs|ColumnTypes|ColumnRenderers|Schemas|Schema|Definitions|Definition|Config|Configuration|Defaults|Default|Tokens|Palette|Context|Provider|Providers|Logic|Scene|Page|Layout)\.(t|j)sx?$/;
|
|
46086
|
-
const HOOK_FILE_BASENAME_PATTERN = /^use[A-Z][\w-]*\.(t|j)sx?$/;
|
|
46087
|
-
const NODE_DEFINITION_BASENAME_PATTERN = /^[A-Z][\w-]*(NodeUtil|ShapeUtil|EdgeUtil|BindingUtil|InlineNode|BlockNode|NotebookNode)\.(t|j)sx?$/;
|
|
46088
|
-
const isAssetOrUtilityFile = (filename) => {
|
|
46089
|
-
const lastSlash = Math.max(filename.lastIndexOf("/"), filename.lastIndexOf("\\"));
|
|
46090
|
-
const basename = lastSlash === -1 ? filename : filename.slice(lastSlash + 1);
|
|
46091
|
-
if (ASSET_FILE_BASENAME_PATTERN.test(basename)) return true;
|
|
46092
|
-
if (UTILITY_FILE_BASENAMES.has(basename)) return true;
|
|
46093
|
-
if (UTILITY_BASENAME_SUFFIX_PATTERN.test(basename)) return true;
|
|
46094
|
-
if (HOOK_FILE_BASENAME_PATTERN.test(basename)) return true;
|
|
46095
|
-
if (NODE_DEFINITION_BASENAME_PATTERN.test(basename)) return true;
|
|
46096
|
-
return false;
|
|
46097
|
-
};
|
|
46098
46999
|
const isFileNameAllowed = (filename, checkJS) => {
|
|
46099
47000
|
if (!filename) return true;
|
|
47001
|
+
if (TEST_SUPPORT_FILE_PATTERN.test(filename)) return false;
|
|
46100
47002
|
if (filename.includes(".test.") || filename.includes(".spec.") || filename.includes(".cy.") || filename.includes(".stories.")) return false;
|
|
46101
47003
|
for (const segment of NON_FAST_REFRESH_PATH_SEGMENTS) if (filename.includes(segment)) return false;
|
|
46102
|
-
if (isEntryPointFile(filename)) return false;
|
|
46103
|
-
if (isFrameworkRouteOrSpecialFilename(filename)) return false;
|
|
46104
|
-
if (isAssetOrUtilityFile(filename)) return false;
|
|
46105
47004
|
if (filename.endsWith(".tsx") || filename.endsWith(".jsx")) return true;
|
|
46106
47005
|
if (checkJS && filename.endsWith(".js")) return true;
|
|
46107
47006
|
return false;
|
|
@@ -46114,16 +47013,100 @@ const onlyExportComponents = defineRule({
|
|
|
46114
47013
|
category: "Architecture",
|
|
46115
47014
|
create: (context) => {
|
|
46116
47015
|
const settings = resolveSettings$6(context.settings);
|
|
46117
|
-
|
|
47016
|
+
const filename = normalizeFilename(context.filename ?? "");
|
|
47017
|
+
const fastRefreshStatus = getFastRefreshFileStatus(context);
|
|
47018
|
+
if (!fastRefreshStatus.isActive) return {};
|
|
47019
|
+
if (isFrameworkRouteOrSpecialFilename(context, fastRefreshStatus.runtime)) return {};
|
|
47020
|
+
if (!isFileNameAllowed(filename, settings.checkJS)) return {};
|
|
47021
|
+
const allowedRouteExportNames = fastRefreshStatus.runtime === "next" ? NEXT_ALLOWED_EXPORT_NAMES : fastRefreshStatus.runtime === "expo" ? EXPO_ALLOWED_EXPORT_NAMES : fastRefreshStatus.runtime === "react-router" || fastRefreshStatus.runtime === "remix" ? REACT_ROUTER_ALLOWED_EXPORT_NAMES : EMPTY_NAME_SET;
|
|
47022
|
+
const routeFactoryMemberNames = fastRefreshStatus.runtime === "tanstack" ? TANSTACK_ROUTE_FACTORY_CALLEE_NAMES : fastRefreshStatus.runtime === "react-router" || fastRefreshStatus.runtime === "remix" ? REACT_ROUTER_FACTORY_CALLEE_NAMES : EMPTY_NAME_SET;
|
|
47023
|
+
const routeFactoryLocalNames = /* @__PURE__ */ new Set();
|
|
47024
|
+
const routeFactoryNamespaceNames = /* @__PURE__ */ new Set();
|
|
47025
|
+
const nextDynamicImportSymbolIds = /* @__PURE__ */ new Set();
|
|
47026
|
+
const importSymbolIds = /* @__PURE__ */ new Set();
|
|
47027
|
+
const routeFactoryBindings = {
|
|
47028
|
+
localNames: routeFactoryLocalNames,
|
|
47029
|
+
memberNames: routeFactoryMemberNames,
|
|
47030
|
+
namespaceNames: routeFactoryNamespaceNames
|
|
47031
|
+
};
|
|
46118
47032
|
const exportNodes = [];
|
|
46119
47033
|
const componentCandidates = [];
|
|
47034
|
+
const createRootNames = /* @__PURE__ */ new Set();
|
|
47035
|
+
const hydrateRootNames = /* @__PURE__ */ new Set();
|
|
47036
|
+
const legacyRenderNames = /* @__PURE__ */ new Set();
|
|
47037
|
+
const reactDomNamespaceNames = /* @__PURE__ */ new Set();
|
|
47038
|
+
const rootNames = /* @__PURE__ */ new Set();
|
|
47039
|
+
let hasRootMount = false;
|
|
47040
|
+
const isCreateRootCall = (node) => {
|
|
47041
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
47042
|
+
if (isNodeOfType(node.callee, "Identifier")) return createRootNames.has(node.callee.name);
|
|
47043
|
+
return isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && reactDomNamespaceNames.has(node.callee.object.name) && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "createRoot";
|
|
47044
|
+
};
|
|
47045
|
+
const visitImportDeclaration = (node) => {
|
|
47046
|
+
if (!isNodeOfType(node, "ImportDeclaration")) return;
|
|
47047
|
+
const source = node.source.value;
|
|
47048
|
+
for (const specifier of node.specifiers) {
|
|
47049
|
+
const symbol = context.scopes.symbolFor(specifier.local);
|
|
47050
|
+
if (symbol) importSymbolIds.add(symbol.id);
|
|
47051
|
+
}
|
|
47052
|
+
if (typeof source === "string" && (fastRefreshStatus.runtime === "tanstack" && (source.startsWith("@tanstack/react-router") || source.startsWith("@tanstack/react-start")) || (fastRefreshStatus.runtime === "react-router" || fastRefreshStatus.runtime === "remix") && (source === "react-router" || source === "react-router-dom" || source === "@remix-run/react"))) for (const specifier of node.specifiers) {
|
|
47053
|
+
if (isNodeOfType(specifier, "ImportNamespaceSpecifier")) {
|
|
47054
|
+
routeFactoryNamespaceNames.add(specifier.local.name);
|
|
47055
|
+
continue;
|
|
47056
|
+
}
|
|
47057
|
+
const importedName = getImportedName(specifier);
|
|
47058
|
+
if (importedName && routeFactoryMemberNames.has(importedName)) routeFactoryLocalNames.add(specifier.local.name);
|
|
47059
|
+
}
|
|
47060
|
+
if (source === "next/dynamic") for (const specifier of node.specifiers) {
|
|
47061
|
+
if (!isNodeOfType(specifier, "ImportDefaultSpecifier")) continue;
|
|
47062
|
+
const symbol = context.scopes.symbolFor(specifier.local);
|
|
47063
|
+
if (symbol) nextDynamicImportSymbolIds.add(symbol.id);
|
|
47064
|
+
}
|
|
47065
|
+
if (source !== "react-dom" && source !== "react-dom/client") return;
|
|
47066
|
+
for (const specifier of node.specifiers) {
|
|
47067
|
+
if (isNodeOfType(specifier, "ImportDefaultSpecifier") || isNodeOfType(specifier, "ImportNamespaceSpecifier")) {
|
|
47068
|
+
reactDomNamespaceNames.add(specifier.local.name);
|
|
47069
|
+
continue;
|
|
47070
|
+
}
|
|
47071
|
+
const importedName = getImportedName(specifier);
|
|
47072
|
+
if (importedName === "createRoot") createRootNames.add(specifier.local.name);
|
|
47073
|
+
if (importedName === "hydrateRoot") hydrateRootNames.add(specifier.local.name);
|
|
47074
|
+
if (source === "react-dom" && (importedName === "render" || importedName === "hydrate")) legacyRenderNames.add(specifier.local.name);
|
|
47075
|
+
}
|
|
47076
|
+
};
|
|
47077
|
+
const visitCallExpression = (node) => {
|
|
47078
|
+
if (!isNodeOfType(node, "CallExpression")) return;
|
|
47079
|
+
if (isInsideFunctionScope(node)) return;
|
|
47080
|
+
if (isNodeOfType(node.callee, "Identifier")) {
|
|
47081
|
+
if (hydrateRootNames.has(node.callee.name) || legacyRenderNames.has(node.callee.name)) hasRootMount = true;
|
|
47082
|
+
return;
|
|
47083
|
+
}
|
|
47084
|
+
if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier")) return;
|
|
47085
|
+
const methodName = node.callee.property.name;
|
|
47086
|
+
if (isNodeOfType(node.callee.object, "Identifier") && reactDomNamespaceNames.has(node.callee.object.name) && (methodName === "render" || methodName === "hydrate" || methodName === "hydrateRoot")) {
|
|
47087
|
+
hasRootMount = true;
|
|
47088
|
+
return;
|
|
47089
|
+
}
|
|
47090
|
+
if (methodName !== "render") return;
|
|
47091
|
+
if (isCreateRootCall(node.callee.object)) {
|
|
47092
|
+
hasRootMount = true;
|
|
47093
|
+
return;
|
|
47094
|
+
}
|
|
47095
|
+
if (isNodeOfType(node.callee.object, "Identifier") && rootNames.has(node.callee.object.name)) hasRootMount = true;
|
|
47096
|
+
};
|
|
46120
47097
|
const pushExportNode = (node) => {
|
|
46121
47098
|
exportNodes.push(node);
|
|
46122
47099
|
};
|
|
46123
47100
|
const pushComponentCandidate = (node) => {
|
|
46124
47101
|
componentCandidates.push(node);
|
|
47102
|
+
if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && node.init && isCreateRootCall(node.init) && !isInsideFunctionScope(node)) rootNames.add(node.id.name);
|
|
46125
47103
|
};
|
|
46126
47104
|
return {
|
|
47105
|
+
ImportDeclaration: visitImportDeclaration,
|
|
47106
|
+
CallExpression: visitCallExpression,
|
|
47107
|
+
AssignmentExpression(node) {
|
|
47108
|
+
if (isNodeOfType(node.left, "Identifier") && rootNames.has(node.left.name)) rootNames.delete(node.left.name);
|
|
47109
|
+
},
|
|
46127
47110
|
ExportAllDeclaration: pushExportNode,
|
|
46128
47111
|
ExportDefaultDeclaration: pushExportNode,
|
|
46129
47112
|
ExportNamedDeclaration: pushExportNode,
|
|
@@ -46131,31 +47114,53 @@ const onlyExportComponents = defineRule({
|
|
|
46131
47114
|
VariableDeclarator: pushComponentCandidate,
|
|
46132
47115
|
ClassDeclaration: pushComponentCandidate,
|
|
46133
47116
|
"Program:exit"() {
|
|
47117
|
+
if (hasRootMount) return;
|
|
46134
47118
|
const localComponentNames = /* @__PURE__ */ new Set();
|
|
47119
|
+
const componentFactorySymbolIds = /* @__PURE__ */ new Set();
|
|
47120
|
+
for (const child of componentCandidates) {
|
|
47121
|
+
if (isInsideFunctionScope(child)) continue;
|
|
47122
|
+
if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
|
|
47123
|
+
if (functionReturnsNextDynamicComponent(child, nextDynamicImportSymbolIds, context.scopes)) {
|
|
47124
|
+
const symbol = context.scopes.symbolFor(child.id);
|
|
47125
|
+
if (symbol?.references.every((reference) => reference.flag === "read")) componentFactorySymbolIds.add(symbol.id);
|
|
47126
|
+
}
|
|
47127
|
+
continue;
|
|
47128
|
+
}
|
|
47129
|
+
if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier") && child.init && functionReturnsNextDynamicComponent(child.init, nextDynamicImportSymbolIds, context.scopes)) {
|
|
47130
|
+
const symbol = context.scopes.symbolFor(child.id);
|
|
47131
|
+
if (symbol?.references.every((reference) => reference.flag === "read")) componentFactorySymbolIds.add(symbol.id);
|
|
47132
|
+
}
|
|
47133
|
+
}
|
|
46135
47134
|
const state = {
|
|
46136
47135
|
customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
|
|
46137
47136
|
allowExportNames: new Set(settings.allowExportNames),
|
|
46138
47137
|
allowConstantExport: settings.allowConstantExport,
|
|
47138
|
+
allowedRouteExportNames,
|
|
47139
|
+
routeFactoryBindings,
|
|
47140
|
+
componentFactorySymbolIds,
|
|
47141
|
+
importSymbolIds,
|
|
46139
47142
|
localComponentNames,
|
|
46140
47143
|
scopes: context.scopes,
|
|
46141
47144
|
controlFlow: context.cfg
|
|
46142
47145
|
};
|
|
46143
47146
|
for (const child of componentCandidates) {
|
|
46144
47147
|
if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
|
|
46145
|
-
if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child) &&
|
|
47148
|
+
if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child) && functionHasReactRenderSemantics(child, state)) localComponentNames.add(child.id.name);
|
|
46146
47149
|
}
|
|
46147
47150
|
if (isNodeOfType(child, "ClassDeclaration") && child.id) {
|
|
46148
47151
|
if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
|
|
46149
47152
|
}
|
|
46150
47153
|
if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
|
|
46151
47154
|
const initializer = child.init;
|
|
46152
|
-
|
|
46153
|
-
|
|
46154
|
-
|
|
47155
|
+
const expression = initializer ? skipTsExpression(initializer) : null;
|
|
47156
|
+
const isDirectFunction = expression !== null && (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression"));
|
|
47157
|
+
if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (expression ? isEs6Component(expression) : false)) && !isInsideFunctionScope(child)) {
|
|
47158
|
+
if (!isDirectFunction || functionHasReactRenderSemantics(expression, state)) localComponentNames.add(child.id.name);
|
|
46155
47159
|
}
|
|
46156
47160
|
}
|
|
46157
47161
|
}
|
|
46158
47162
|
const exports = [];
|
|
47163
|
+
const exportAllNodes = [];
|
|
46159
47164
|
let hasReactExport = false;
|
|
46160
47165
|
let hasAnyExports = false;
|
|
46161
47166
|
const isExportedNodeIds = /* @__PURE__ */ new WeakSet();
|
|
@@ -46163,10 +47168,8 @@ const onlyExportComponents = defineRule({
|
|
|
46163
47168
|
if (isNodeOfType(child, "ExportAllDeclaration")) {
|
|
46164
47169
|
if (child.exportKind === "type") continue;
|
|
46165
47170
|
hasAnyExports = true;
|
|
46166
|
-
|
|
46167
|
-
|
|
46168
|
-
message: EXPORT_ALL_MESSAGE
|
|
46169
|
-
});
|
|
47171
|
+
const source = child.source.value;
|
|
47172
|
+
if (typeof source !== "string" || exportAllAddsRuntimeValues(filename, source)) exportAllNodes.push(child);
|
|
46170
47173
|
continue;
|
|
46171
47174
|
}
|
|
46172
47175
|
if (isNodeOfType(child, "ExportDefaultDeclaration")) {
|
|
@@ -46174,17 +47177,24 @@ const onlyExportComponents = defineRule({
|
|
|
46174
47177
|
const declaration = child.declaration;
|
|
46175
47178
|
const stripped = skipTsExpression(declaration);
|
|
46176
47179
|
if (isNodeOfType(stripped, "FunctionDeclaration") || isNodeOfType(stripped, "FunctionExpression")) {
|
|
47180
|
+
const hasRenderOutput = functionHasReactRenderSemantics(stripped, state);
|
|
46177
47181
|
if (stripped.id) {
|
|
46178
47182
|
const idNode = stripped.id;
|
|
46179
47183
|
isExportedNodeIds.add(stripped);
|
|
46180
|
-
exports.push(classifyExport(idNode.name, idNode, true, null, state)
|
|
46181
|
-
|
|
47184
|
+
exports.push(hasRenderOutput ? classifyExport(idNode.name, idNode, true, null, state) : {
|
|
47185
|
+
kind: "non-component",
|
|
47186
|
+
reportNode: idNode
|
|
47187
|
+
});
|
|
47188
|
+
} else if (hasRenderOutput) {
|
|
46182
47189
|
context.report({
|
|
46183
47190
|
node: stripped,
|
|
46184
47191
|
message: ANONYMOUS_MESSAGE
|
|
46185
47192
|
});
|
|
46186
47193
|
hasReactExport = true;
|
|
46187
|
-
}
|
|
47194
|
+
} else exports.push({
|
|
47195
|
+
kind: "non-component",
|
|
47196
|
+
reportNode: stripped
|
|
47197
|
+
});
|
|
46188
47198
|
continue;
|
|
46189
47199
|
}
|
|
46190
47200
|
if (isNodeOfType(stripped, "ClassDeclaration") || isNodeOfType(stripped, "ClassExpression")) {
|
|
@@ -46203,26 +47213,34 @@ const onlyExportComponents = defineRule({
|
|
|
46203
47213
|
continue;
|
|
46204
47214
|
}
|
|
46205
47215
|
if (isNodeOfType(stripped, "Identifier")) {
|
|
46206
|
-
exports.push(
|
|
47216
|
+
exports.push(isProvenComponentValue(stripped, state) ? { kind: "react-component" } : {
|
|
47217
|
+
kind: "non-component",
|
|
47218
|
+
reportNode: stripped
|
|
47219
|
+
});
|
|
47220
|
+
continue;
|
|
47221
|
+
}
|
|
47222
|
+
if (isNodeOfType(stripped, "MemberExpression")) {
|
|
47223
|
+
if (isProvenComponentValue(stripped, state)) hasReactExport = true;
|
|
47224
|
+
else exports.push({
|
|
47225
|
+
kind: "non-component",
|
|
47226
|
+
reportNode: stripped
|
|
47227
|
+
});
|
|
46207
47228
|
continue;
|
|
46208
47229
|
}
|
|
46209
47230
|
if (isNodeOfType(stripped, "CallExpression")) {
|
|
46210
|
-
if (isRouteFactoryCall(stripped)) {
|
|
47231
|
+
if (isRouteFactoryCall(stripped, state.routeFactoryBindings)) {
|
|
46211
47232
|
hasReactExport = true;
|
|
46212
47233
|
continue;
|
|
46213
47234
|
}
|
|
46214
|
-
|
|
46215
|
-
|
|
46216
|
-
|
|
46217
|
-
|
|
46218
|
-
|
|
46219
|
-
|
|
46220
|
-
|
|
46221
|
-
|
|
46222
|
-
|
|
46223
|
-
})();
|
|
46224
|
-
if (isHoc && firstArgIsValid) hasReactExport = true;
|
|
46225
|
-
else if (!isHoc && isConfigOnlyFactoryCall(stripped)) exports.push({
|
|
47235
|
+
if (isReactCreateContext(stripped)) {
|
|
47236
|
+
exports.push({
|
|
47237
|
+
kind: "react-context",
|
|
47238
|
+
reportNode: stripped
|
|
47239
|
+
});
|
|
47240
|
+
continue;
|
|
47241
|
+
}
|
|
47242
|
+
if (isDirectRefreshWrapperCall(stripped, state)) hasReactExport = true;
|
|
47243
|
+
else if (isConfigOnlyFactoryCall(stripped)) exports.push({
|
|
46226
47244
|
kind: "non-component",
|
|
46227
47245
|
reportNode: stripped
|
|
46228
47246
|
});
|
|
@@ -46233,16 +47251,32 @@ const onlyExportComponents = defineRule({
|
|
|
46233
47251
|
continue;
|
|
46234
47252
|
}
|
|
46235
47253
|
if (isNodeOfType(stripped, "ObjectExpression")) {
|
|
46236
|
-
|
|
46237
|
-
|
|
46238
|
-
|
|
47254
|
+
exports.push(objectExpressionBundlesComponents(stripped, state) ? {
|
|
47255
|
+
kind: "namespace-object",
|
|
47256
|
+
reportNode: stripped
|
|
47257
|
+
} : {
|
|
47258
|
+
kind: "non-component",
|
|
47259
|
+
reportNode: stripped
|
|
46239
47260
|
});
|
|
46240
47261
|
continue;
|
|
46241
47262
|
}
|
|
46242
|
-
if (isNodeOfType(stripped, "ArrowFunctionExpression")
|
|
46243
|
-
|
|
46244
|
-
|
|
46245
|
-
|
|
47263
|
+
if (isNodeOfType(stripped, "ArrowFunctionExpression")) {
|
|
47264
|
+
if (functionHasReactRenderSemantics(stripped, state)) {
|
|
47265
|
+
context.report({
|
|
47266
|
+
node: stripped,
|
|
47267
|
+
message: ANONYMOUS_MESSAGE
|
|
47268
|
+
});
|
|
47269
|
+
hasReactExport = true;
|
|
47270
|
+
} else exports.push({
|
|
47271
|
+
kind: "non-component",
|
|
47272
|
+
reportNode: stripped
|
|
47273
|
+
});
|
|
47274
|
+
continue;
|
|
47275
|
+
}
|
|
47276
|
+
if (isNodeOfType(stripped, "Literal") || isNodeOfType(stripped, "NewExpression")) {
|
|
47277
|
+
exports.push({
|
|
47278
|
+
kind: "non-component",
|
|
47279
|
+
reportNode: stripped
|
|
46246
47280
|
});
|
|
46247
47281
|
continue;
|
|
46248
47282
|
}
|
|
@@ -46259,7 +47293,10 @@ const onlyExportComponents = defineRule({
|
|
|
46259
47293
|
const declaration = child.declaration;
|
|
46260
47294
|
if (isNodeOfType(declaration, "FunctionDeclaration") && declaration.id) {
|
|
46261
47295
|
isExportedNodeIds.add(declaration);
|
|
46262
|
-
exports.push(classifyExport(declaration.id.name, declaration.id, true, null, state)
|
|
47296
|
+
exports.push(functionHasReactRenderSemantics(declaration, state) || localComponentNames.has(declaration.id.name) ? classifyExport(declaration.id.name, declaration.id, true, null, state) : {
|
|
47297
|
+
kind: "non-component",
|
|
47298
|
+
reportNode: declaration.id
|
|
47299
|
+
});
|
|
46263
47300
|
} else if (isNodeOfType(declaration, "ClassDeclaration") && declaration.id) {
|
|
46264
47301
|
isExportedNodeIds.add(declaration);
|
|
46265
47302
|
if (isReactComponentName(declaration.id.name) && isEs6Component(declaration)) exports.push({ kind: "react-component" });
|
|
@@ -46283,6 +47320,7 @@ const onlyExportComponents = defineRule({
|
|
|
46283
47320
|
const isReExportFromSource = Boolean(child.source);
|
|
46284
47321
|
for (const specifier of child.specifiers ?? []) {
|
|
46285
47322
|
if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
|
|
47323
|
+
if (specifier.exportKind === "type") continue;
|
|
46286
47324
|
const exported = specifier.exported;
|
|
46287
47325
|
const local = specifier.local;
|
|
46288
47326
|
let exportedName = null;
|
|
@@ -46290,8 +47328,13 @@ const onlyExportComponents = defineRule({
|
|
|
46290
47328
|
const localName = local && isNodeOfType(local, "Identifier") ? local.name : null;
|
|
46291
47329
|
const reportNode = specifier;
|
|
46292
47330
|
let entry;
|
|
46293
|
-
if (
|
|
46294
|
-
else if (exportedName) entry =
|
|
47331
|
+
if (localName && localComponentNames.has(localName)) entry = { kind: "react-component" };
|
|
47332
|
+
else if (!isReExportFromSource && localName === exportedName && localName !== null && isReactComponentName(localName)) entry = { kind: "react-component" };
|
|
47333
|
+
else if (exportedName === "default" && localName && local) entry = isProvenComponentValue(local, state) ? { kind: "react-component" } : {
|
|
47334
|
+
kind: "non-component",
|
|
47335
|
+
reportNode
|
|
47336
|
+
};
|
|
47337
|
+
else if (exportedName) entry = classifyExport(exportedName, reportNode, false, isReExportFromSource ? null : local, state);
|
|
46295
47338
|
else {
|
|
46296
47339
|
entry = {
|
|
46297
47340
|
kind: "non-component",
|
|
@@ -46309,15 +47352,21 @@ const onlyExportComponents = defineRule({
|
|
|
46309
47352
|
node: entry.reportNode,
|
|
46310
47353
|
message: NAMESPACE_OBJECT_MESSAGE
|
|
46311
47354
|
});
|
|
46312
|
-
if (hasAnyExports && hasReactExport)
|
|
46313
|
-
|
|
46314
|
-
node:
|
|
46315
|
-
message:
|
|
46316
|
-
});
|
|
46317
|
-
if (entry.kind === "react-context") context.report({
|
|
46318
|
-
node: entry.reportNode,
|
|
46319
|
-
message: REACT_CONTEXT_MESSAGE
|
|
47355
|
+
if (hasAnyExports && hasReactExport) {
|
|
47356
|
+
for (const exportAllNode of exportAllNodes) context.report({
|
|
47357
|
+
node: exportAllNode,
|
|
47358
|
+
message: EXPORT_ALL_MESSAGE
|
|
46320
47359
|
});
|
|
47360
|
+
for (const entry of exports) {
|
|
47361
|
+
if (entry.kind === "non-component") context.report({
|
|
47362
|
+
node: entry.reportNode,
|
|
47363
|
+
message: NAMED_EXPORT_MESSAGE
|
|
47364
|
+
});
|
|
47365
|
+
if (entry.kind === "react-context") context.report({
|
|
47366
|
+
node: entry.reportNode,
|
|
47367
|
+
message: REACT_CONTEXT_MESSAGE
|
|
47368
|
+
});
|
|
47369
|
+
}
|
|
46321
47370
|
}
|
|
46322
47371
|
}
|
|
46323
47372
|
};
|
|
@@ -50966,6 +52015,357 @@ const rerenderMemoBeforeEarlyReturn = defineRule({
|
|
|
50966
52015
|
}
|
|
50967
52016
|
});
|
|
50968
52017
|
//#endregion
|
|
52018
|
+
//#region src/plugin/utils/build-same-file-memo-comparator-registry.ts
|
|
52019
|
+
const unwrapTopLevelDeclaration = (statement) => {
|
|
52020
|
+
if (isNodeOfType(statement, "ExportNamedDeclaration")) return statement.declaration;
|
|
52021
|
+
return statement;
|
|
52022
|
+
};
|
|
52023
|
+
const buildSameFileMemoComparatorRegistry = (program) => {
|
|
52024
|
+
const registry = /* @__PURE__ */ new Map();
|
|
52025
|
+
if (!isNodeOfType(program, "Program")) return registry;
|
|
52026
|
+
const memoBindings = /* @__PURE__ */ new Set();
|
|
52027
|
+
const reactNamespaceBindings = /* @__PURE__ */ new Set();
|
|
52028
|
+
for (const statement of program.body) {
|
|
52029
|
+
if (!isNodeOfType(statement, "ImportDeclaration") || statement.source.value !== "react") continue;
|
|
52030
|
+
for (const specifier of statement.specifiers) if (isNodeOfType(specifier, "ImportDefaultSpecifier") || isNodeOfType(specifier, "ImportNamespaceSpecifier")) reactNamespaceBindings.add(specifier.local.name);
|
|
52031
|
+
else if (isNodeOfType(specifier, "ImportSpecifier") && getImportedName(specifier) === "memo") memoBindings.add(specifier.local.name);
|
|
52032
|
+
}
|
|
52033
|
+
for (const statement of program.body) {
|
|
52034
|
+
const declaration = unwrapTopLevelDeclaration(statement);
|
|
52035
|
+
if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) continue;
|
|
52036
|
+
for (const declarator of declaration.declarations ?? []) {
|
|
52037
|
+
if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.id, "Identifier") || !declarator.init) continue;
|
|
52038
|
+
const initializer = stripParenExpression(declarator.init);
|
|
52039
|
+
if (!isNodeOfType(initializer, "CallExpression")) continue;
|
|
52040
|
+
const callee = stripParenExpression(initializer.callee);
|
|
52041
|
+
const isNamedMemo = isNodeOfType(callee, "Identifier") && memoBindings.has(callee.name);
|
|
52042
|
+
const isNamespaceMemo = isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.object, "Identifier") && reactNamespaceBindings.has(callee.object.name) && isNodeOfType(callee.property, "Identifier") && callee.property.name === "memo";
|
|
52043
|
+
if (!isNamedMemo && !isNamespaceMemo) continue;
|
|
52044
|
+
const comparator = initializer.arguments?.[1];
|
|
52045
|
+
if (!comparator || isNodeOfType(comparator, "SpreadElement")) continue;
|
|
52046
|
+
registry.set(declarator.id.name, {
|
|
52047
|
+
bindingIdentifier: declarator.id,
|
|
52048
|
+
comparator
|
|
52049
|
+
});
|
|
52050
|
+
}
|
|
52051
|
+
}
|
|
52052
|
+
return registry;
|
|
52053
|
+
};
|
|
52054
|
+
//#endregion
|
|
52055
|
+
//#region src/plugin/utils/comparator-proves-empty-prop-equality.ts
|
|
52056
|
+
const UNKNOWN_VALUE = { kind: "unknown" };
|
|
52057
|
+
const TRUE_VALUE = {
|
|
52058
|
+
kind: "boolean",
|
|
52059
|
+
value: true
|
|
52060
|
+
};
|
|
52061
|
+
const FALSE_VALUE = {
|
|
52062
|
+
kind: "boolean",
|
|
52063
|
+
value: false
|
|
52064
|
+
};
|
|
52065
|
+
const UNDEFINED_VALUE = { kind: "undefined" };
|
|
52066
|
+
const PREVIOUS_PROPS_VALUE = { kind: "previous-props" };
|
|
52067
|
+
const NEXT_PROPS_VALUE = { kind: "next-props" };
|
|
52068
|
+
const OBJECT_PROTOTYPE_PROPERTY_NAMES = new Set([
|
|
52069
|
+
"__defineGetter__",
|
|
52070
|
+
"__defineSetter__",
|
|
52071
|
+
"__lookupGetter__",
|
|
52072
|
+
"__lookupSetter__",
|
|
52073
|
+
"__proto__",
|
|
52074
|
+
"constructor",
|
|
52075
|
+
"hasOwnProperty",
|
|
52076
|
+
"isPrototypeOf",
|
|
52077
|
+
"propertyIsEnumerable",
|
|
52078
|
+
"toLocaleString",
|
|
52079
|
+
"toString",
|
|
52080
|
+
"valueOf"
|
|
52081
|
+
]);
|
|
52082
|
+
const PROVABLY_CALLABLE_GLOBAL_NAMES = new Set([
|
|
52083
|
+
"Boolean",
|
|
52084
|
+
"Number",
|
|
52085
|
+
"String"
|
|
52086
|
+
]);
|
|
52087
|
+
const booleanValue = (value) => value ? TRUE_VALUE : FALSE_VALUE;
|
|
52088
|
+
const constantBooleanFormula = (value) => ({
|
|
52089
|
+
kind: "constant",
|
|
52090
|
+
value
|
|
52091
|
+
});
|
|
52092
|
+
const getBooleanFormula = (value) => {
|
|
52093
|
+
if (value.kind === "boolean") return constantBooleanFormula(value.value === true);
|
|
52094
|
+
return value.kind === "boolean-formula" && value.formula ? value.formula : null;
|
|
52095
|
+
};
|
|
52096
|
+
const booleanFormulaValue = (formula) => formula.kind === "constant" ? booleanValue(formula.value === true) : {
|
|
52097
|
+
kind: "boolean-formula",
|
|
52098
|
+
formula
|
|
52099
|
+
};
|
|
52100
|
+
const negateBooleanFormula = (formula) => {
|
|
52101
|
+
if (formula.kind === "constant") return constantBooleanFormula(formula.value !== true);
|
|
52102
|
+
if (formula.kind === "not" && formula.operand) return formula.operand;
|
|
52103
|
+
return {
|
|
52104
|
+
kind: "not",
|
|
52105
|
+
operand: formula
|
|
52106
|
+
};
|
|
52107
|
+
};
|
|
52108
|
+
const combineBooleanFormulas = (operator, left, right) => {
|
|
52109
|
+
if (left.kind === "constant") {
|
|
52110
|
+
if (operator === "and") return left.value === true ? right : left;
|
|
52111
|
+
return left.value === true ? left : right;
|
|
52112
|
+
}
|
|
52113
|
+
if (right.kind === "constant") {
|
|
52114
|
+
if (operator === "and") return right.value === true ? left : right;
|
|
52115
|
+
return right.value === true ? right : left;
|
|
52116
|
+
}
|
|
52117
|
+
return {
|
|
52118
|
+
kind: operator,
|
|
52119
|
+
left,
|
|
52120
|
+
right
|
|
52121
|
+
};
|
|
52122
|
+
};
|
|
52123
|
+
const conditionalBooleanFormula = (test, consequent, alternate) => {
|
|
52124
|
+
if (test.kind === "constant") return test.value === true ? consequent : alternate;
|
|
52125
|
+
return {
|
|
52126
|
+
kind: "conditional",
|
|
52127
|
+
test,
|
|
52128
|
+
consequent,
|
|
52129
|
+
alternate
|
|
52130
|
+
};
|
|
52131
|
+
};
|
|
52132
|
+
const collectBooleanFormulaAtomKeys = (formula, atomKeys) => {
|
|
52133
|
+
if (formula.kind === "atom") {
|
|
52134
|
+
if (formula.atomKey) atomKeys.add(formula.atomKey);
|
|
52135
|
+
return;
|
|
52136
|
+
}
|
|
52137
|
+
if (formula.operand) collectBooleanFormulaAtomKeys(formula.operand, atomKeys);
|
|
52138
|
+
if (formula.left) collectBooleanFormulaAtomKeys(formula.left, atomKeys);
|
|
52139
|
+
if (formula.right) collectBooleanFormulaAtomKeys(formula.right, atomKeys);
|
|
52140
|
+
if (formula.test) collectBooleanFormulaAtomKeys(formula.test, atomKeys);
|
|
52141
|
+
if (formula.consequent) collectBooleanFormulaAtomKeys(formula.consequent, atomKeys);
|
|
52142
|
+
if (formula.alternate) collectBooleanFormulaAtomKeys(formula.alternate, atomKeys);
|
|
52143
|
+
};
|
|
52144
|
+
const evaluateBooleanFormula = (formula, atomValues) => {
|
|
52145
|
+
if (formula.kind === "constant") return formula.value ?? null;
|
|
52146
|
+
if (formula.kind === "atom") return formula.atomKey ? atomValues.get(formula.atomKey) ?? null : null;
|
|
52147
|
+
if (formula.kind === "not") {
|
|
52148
|
+
const operand = formula.operand ? evaluateBooleanFormula(formula.operand, atomValues) : null;
|
|
52149
|
+
return operand === null ? null : !operand;
|
|
52150
|
+
}
|
|
52151
|
+
if (formula.kind === "and" || formula.kind === "or") {
|
|
52152
|
+
const left = formula.left ? evaluateBooleanFormula(formula.left, atomValues) : null;
|
|
52153
|
+
const right = formula.right ? evaluateBooleanFormula(formula.right, atomValues) : null;
|
|
52154
|
+
if (left === null || right === null) return null;
|
|
52155
|
+
return formula.kind === "and" ? left && right : left || right;
|
|
52156
|
+
}
|
|
52157
|
+
const test = formula.test ? evaluateBooleanFormula(formula.test, atomValues) : null;
|
|
52158
|
+
if (test === null) return null;
|
|
52159
|
+
const branch = test ? formula.consequent : formula.alternate;
|
|
52160
|
+
return branch ? evaluateBooleanFormula(branch, atomValues) : null;
|
|
52161
|
+
};
|
|
52162
|
+
const couldStableTargetReferencePreventRender = (distinctReferenceFormula, sharedReferenceFormula) => {
|
|
52163
|
+
const atomKeys = /* @__PURE__ */ new Set();
|
|
52164
|
+
collectBooleanFormulaAtomKeys(distinctReferenceFormula, atomKeys);
|
|
52165
|
+
collectBooleanFormulaAtomKeys(sharedReferenceFormula, atomKeys);
|
|
52166
|
+
if (atomKeys.size > 8) return true;
|
|
52167
|
+
const orderedAtomKeys = [...atomKeys];
|
|
52168
|
+
const assignmentCount = 2 ** orderedAtomKeys.length;
|
|
52169
|
+
for (let assignmentIndex = 0; assignmentIndex < assignmentCount; assignmentIndex += 1) {
|
|
52170
|
+
const atomValues = /* @__PURE__ */ new Map();
|
|
52171
|
+
for (const [atomIndex, atomKey] of orderedAtomKeys.entries()) atomValues.set(atomKey, Boolean(assignmentIndex & 1 << atomIndex));
|
|
52172
|
+
const distinctReferenceResult = evaluateBooleanFormula(distinctReferenceFormula, atomValues);
|
|
52173
|
+
const sharedReferenceResult = evaluateBooleanFormula(sharedReferenceFormula, atomValues);
|
|
52174
|
+
if (distinctReferenceResult === null || sharedReferenceResult === null) return true;
|
|
52175
|
+
if (!distinctReferenceResult && sharedReferenceResult) return true;
|
|
52176
|
+
}
|
|
52177
|
+
return false;
|
|
52178
|
+
};
|
|
52179
|
+
const emptyReferenceValue = (kind, referenceOrigin) => ({
|
|
52180
|
+
kind,
|
|
52181
|
+
referenceOrigin
|
|
52182
|
+
});
|
|
52183
|
+
const evaluateEquality = (left, right, emptyReferencesAreEqual, equalityKind) => {
|
|
52184
|
+
if (left.kind === "unknown" || right.kind === "unknown") return UNKNOWN_VALUE;
|
|
52185
|
+
const leftBooleanFormula = getBooleanFormula(left);
|
|
52186
|
+
const rightBooleanFormula = getBooleanFormula(right);
|
|
52187
|
+
if (leftBooleanFormula || rightBooleanFormula) {
|
|
52188
|
+
if (!leftBooleanFormula || !rightBooleanFormula) return UNKNOWN_VALUE;
|
|
52189
|
+
return booleanFormulaValue(combineBooleanFormulas("or", combineBooleanFormulas("and", leftBooleanFormula, rightBooleanFormula), combineBooleanFormulas("and", negateBooleanFormula(leftBooleanFormula), negateBooleanFormula(rightBooleanFormula))));
|
|
52190
|
+
}
|
|
52191
|
+
if (left.kind === "prop-symbol" || right.kind === "prop-symbol") {
|
|
52192
|
+
if (left.kind !== "prop-symbol" || right.kind !== "prop-symbol") return UNKNOWN_VALUE;
|
|
52193
|
+
if (left.propOwner === right.propOwner && left.value === right.value) return TRUE_VALUE;
|
|
52194
|
+
if (left.propOwner === right.propOwner || left.value !== right.value) return UNKNOWN_VALUE;
|
|
52195
|
+
return booleanFormulaValue({
|
|
52196
|
+
kind: "atom",
|
|
52197
|
+
atomKey: `${equalityKind}:${String(left.value)}`
|
|
52198
|
+
});
|
|
52199
|
+
}
|
|
52200
|
+
if (left.kind === "empty-array" || left.kind === "empty-object") {
|
|
52201
|
+
if (left.kind !== right.kind) return FALSE_VALUE;
|
|
52202
|
+
if (left.referenceOrigin === right.referenceOrigin) return TRUE_VALUE;
|
|
52203
|
+
return booleanValue((left.referenceOrigin === "previous-target" && right.referenceOrigin === "next-target" || left.referenceOrigin === "next-target" && right.referenceOrigin === "previous-target") && emptyReferencesAreEqual);
|
|
52204
|
+
}
|
|
52205
|
+
if (right.kind === "empty-array" || right.kind === "empty-object") return FALSE_VALUE;
|
|
52206
|
+
if (left.kind !== right.kind) return FALSE_VALUE;
|
|
52207
|
+
if (left.kind === "previous-props" || left.kind === "next-props") return UNKNOWN_VALUE;
|
|
52208
|
+
if (left.kind === "symbol" && left.value !== right.value) return UNKNOWN_VALUE;
|
|
52209
|
+
return booleanValue(left.value === right.value);
|
|
52210
|
+
};
|
|
52211
|
+
const getReturnedExpression = (functionNode) => {
|
|
52212
|
+
if (!isFunctionLike$1(functionNode)) return null;
|
|
52213
|
+
if (isNodeOfType(functionNode, "ArrowFunctionExpression")) {
|
|
52214
|
+
const body = functionNode.body;
|
|
52215
|
+
if (!isNodeOfType(body, "BlockStatement")) return body;
|
|
52216
|
+
}
|
|
52217
|
+
const body = functionNode.body;
|
|
52218
|
+
if (!isNodeOfType(body, "BlockStatement") || body.body.length !== 1) return null;
|
|
52219
|
+
const returnStatement = body.body[0];
|
|
52220
|
+
return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument ? returnStatement.argument : null;
|
|
52221
|
+
};
|
|
52222
|
+
const isProvablyCallable = (expression, state) => {
|
|
52223
|
+
const node = stripParenExpression(expression);
|
|
52224
|
+
if (isFunctionLike$1(node)) return true;
|
|
52225
|
+
if (isNodeOfType(node, "Identifier") && PROVABLY_CALLABLE_GLOBAL_NAMES.has(node.name) && state.scopes.isGlobalReference(node)) return true;
|
|
52226
|
+
return isFunctionLike$1(resolveExactLocalFunction(node, state.scopes));
|
|
52227
|
+
};
|
|
52228
|
+
const evaluateExpression = (expression, state) => {
|
|
52229
|
+
const node = stripParenExpression(expression);
|
|
52230
|
+
if (isNodeOfType(node, "Literal")) {
|
|
52231
|
+
if (typeof node.value === "boolean") return booleanValue(node.value);
|
|
52232
|
+
if (typeof node.value === "number") return {
|
|
52233
|
+
kind: "number",
|
|
52234
|
+
value: node.value
|
|
52235
|
+
};
|
|
52236
|
+
if (typeof node.value === "string") return {
|
|
52237
|
+
kind: "string",
|
|
52238
|
+
value: node.value
|
|
52239
|
+
};
|
|
52240
|
+
return node.value === void 0 ? UNDEFINED_VALUE : UNKNOWN_VALUE;
|
|
52241
|
+
}
|
|
52242
|
+
if (isNodeOfType(node, "Identifier")) {
|
|
52243
|
+
if (node.name === "undefined" && state.scopes.isGlobalReference(node)) return UNDEFINED_VALUE;
|
|
52244
|
+
return state.bindings.get(node.name) ?? UNKNOWN_VALUE;
|
|
52245
|
+
}
|
|
52246
|
+
if (isNodeOfType(node, "MemberExpression")) {
|
|
52247
|
+
const objectValue = evaluateExpression(node.object, state);
|
|
52248
|
+
if ((objectValue.kind === "empty-array" || objectValue.kind === "empty-object") && node.computed && isNodeOfType(node.property, "Literal") && (typeof node.property.value === "number" || typeof node.property.value === "string" && /^\d+$/.test(node.property.value))) return UNDEFINED_VALUE;
|
|
52249
|
+
const propertyName = getStaticPropertyName(node);
|
|
52250
|
+
if (propertyName === null) return UNKNOWN_VALUE;
|
|
52251
|
+
if (objectValue.kind === "previous-props" || objectValue.kind === "next-props") {
|
|
52252
|
+
if (propertyName === state.propName) {
|
|
52253
|
+
const referenceOrigin = objectValue.kind === "previous-props" ? "previous-target" : "next-target";
|
|
52254
|
+
return emptyReferenceValue(state.emptyLiteralKind === "array" ? "empty-array" : "empty-object", referenceOrigin);
|
|
52255
|
+
}
|
|
52256
|
+
return {
|
|
52257
|
+
kind: "prop-symbol",
|
|
52258
|
+
propOwner: objectValue.kind === "previous-props" ? "previous" : "next",
|
|
52259
|
+
value: propertyName
|
|
52260
|
+
};
|
|
52261
|
+
}
|
|
52262
|
+
if (objectValue.kind === "empty-array" && propertyName === "length") return {
|
|
52263
|
+
kind: "number",
|
|
52264
|
+
value: 0
|
|
52265
|
+
};
|
|
52266
|
+
if (objectValue.kind === "empty-object" && !OBJECT_PROTOTYPE_PROPERTY_NAMES.has(propertyName)) return UNDEFINED_VALUE;
|
|
52267
|
+
return UNKNOWN_VALUE;
|
|
52268
|
+
}
|
|
52269
|
+
if (isNodeOfType(node, "UnaryExpression") && node.operator === "!") {
|
|
52270
|
+
const argumentFormula = getBooleanFormula(evaluateExpression(node.argument, state));
|
|
52271
|
+
return argumentFormula ? booleanFormulaValue(negateBooleanFormula(argumentFormula)) : UNKNOWN_VALUE;
|
|
52272
|
+
}
|
|
52273
|
+
if (isNodeOfType(node, "LogicalExpression")) {
|
|
52274
|
+
const leftFormula = getBooleanFormula(evaluateExpression(node.left, state));
|
|
52275
|
+
if (!leftFormula) return UNKNOWN_VALUE;
|
|
52276
|
+
if (node.operator === "&&") {
|
|
52277
|
+
if (leftFormula.kind === "constant" && leftFormula.value === false) return FALSE_VALUE;
|
|
52278
|
+
const rightFormula = getBooleanFormula(evaluateExpression(node.right, state));
|
|
52279
|
+
return rightFormula ? booleanFormulaValue(combineBooleanFormulas("and", leftFormula, rightFormula)) : UNKNOWN_VALUE;
|
|
52280
|
+
}
|
|
52281
|
+
if (node.operator === "||") {
|
|
52282
|
+
if (leftFormula.kind === "constant" && leftFormula.value === true) return TRUE_VALUE;
|
|
52283
|
+
const rightFormula = getBooleanFormula(evaluateExpression(node.right, state));
|
|
52284
|
+
return rightFormula ? booleanFormulaValue(combineBooleanFormulas("or", leftFormula, rightFormula)) : UNKNOWN_VALUE;
|
|
52285
|
+
}
|
|
52286
|
+
return UNKNOWN_VALUE;
|
|
52287
|
+
}
|
|
52288
|
+
if (isNodeOfType(node, "BinaryExpression")) {
|
|
52289
|
+
const left = evaluateExpression(node.left, state);
|
|
52290
|
+
const right = evaluateExpression(node.right, state);
|
|
52291
|
+
if ([
|
|
52292
|
+
"===",
|
|
52293
|
+
"==",
|
|
52294
|
+
"!==",
|
|
52295
|
+
"!="
|
|
52296
|
+
].includes(node.operator)) {
|
|
52297
|
+
const equality = evaluateEquality(left, right, state.emptyReferencesAreEqual, node.operator === "===" || node.operator === "!==" ? "strict" : "loose");
|
|
52298
|
+
if (node.operator === "===" || node.operator === "==") return equality;
|
|
52299
|
+
const equalityFormula = getBooleanFormula(equality);
|
|
52300
|
+
return equalityFormula ? booleanFormulaValue(negateBooleanFormula(equalityFormula)) : UNKNOWN_VALUE;
|
|
52301
|
+
}
|
|
52302
|
+
if (left.kind !== "number" || right.kind !== "number") return UNKNOWN_VALUE;
|
|
52303
|
+
if (node.operator === "<") return booleanValue(Number(left.value) < Number(right.value));
|
|
52304
|
+
if (node.operator === "<=") return booleanValue(Number(left.value) <= Number(right.value));
|
|
52305
|
+
if (node.operator === ">") return booleanValue(Number(left.value) > Number(right.value));
|
|
52306
|
+
if (node.operator === ">=") return booleanValue(Number(left.value) >= Number(right.value));
|
|
52307
|
+
return UNKNOWN_VALUE;
|
|
52308
|
+
}
|
|
52309
|
+
if (isNodeOfType(node, "ConditionalExpression")) {
|
|
52310
|
+
const testFormula = getBooleanFormula(evaluateExpression(node.test, state));
|
|
52311
|
+
if (!testFormula) return UNKNOWN_VALUE;
|
|
52312
|
+
if (testFormula.kind === "constant") return evaluateExpression(testFormula.value === true ? node.consequent : node.alternate, state);
|
|
52313
|
+
const consequentFormula = getBooleanFormula(evaluateExpression(node.consequent, state));
|
|
52314
|
+
const alternateFormula = getBooleanFormula(evaluateExpression(node.alternate, state));
|
|
52315
|
+
return consequentFormula && alternateFormula ? booleanFormulaValue(conditionalBooleanFormula(testFormula, consequentFormula, alternateFormula)) : UNKNOWN_VALUE;
|
|
52316
|
+
}
|
|
52317
|
+
if (!isNodeOfType(node, "CallExpression")) return UNKNOWN_VALUE;
|
|
52318
|
+
const callee = stripParenExpression(node.callee);
|
|
52319
|
+
if (isNodeOfType(callee, "MemberExpression")) {
|
|
52320
|
+
const receiver = evaluateExpression(callee.object, state);
|
|
52321
|
+
const methodName = getStaticPropertyName(callee);
|
|
52322
|
+
const callback = node.arguments[0];
|
|
52323
|
+
if (receiver.kind === "empty-array" && (methodName === "every" || methodName === "some") && callback && !isNodeOfType(callback, "SpreadElement") && isProvablyCallable(callback, state)) return methodName === "every" ? TRUE_VALUE : FALSE_VALUE;
|
|
52324
|
+
if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Object" && state.scopes.isGlobalReference(callee.object) && (methodName === "keys" || methodName === "values") && node.arguments.length === 1 && !isNodeOfType(node.arguments[0], "SpreadElement")) {
|
|
52325
|
+
if (evaluateExpression(node.arguments[0], state).kind === "empty-object") return emptyReferenceValue("empty-array", node);
|
|
52326
|
+
}
|
|
52327
|
+
return UNKNOWN_VALUE;
|
|
52328
|
+
}
|
|
52329
|
+
if (!isNodeOfType(callee, "Identifier")) return UNKNOWN_VALUE;
|
|
52330
|
+
const localFunction = resolveExactLocalFunction(callee, state.scopes);
|
|
52331
|
+
if (!isFunctionLike$1(localFunction) || localFunction.async || localFunction.generator || state.activeFunctions.has(localFunction)) return UNKNOWN_VALUE;
|
|
52332
|
+
const returnedExpression = getReturnedExpression(localFunction);
|
|
52333
|
+
if (!returnedExpression) return UNKNOWN_VALUE;
|
|
52334
|
+
const parameters = localFunction.params ?? [];
|
|
52335
|
+
if (parameters.length !== node.arguments.length) return UNKNOWN_VALUE;
|
|
52336
|
+
const bindings = new Map(state.bindings);
|
|
52337
|
+
for (const [parameterIndex, parameter] of parameters.entries()) {
|
|
52338
|
+
const argument = node.arguments[parameterIndex];
|
|
52339
|
+
if (!isNodeOfType(parameter, "Identifier") || !argument || isNodeOfType(argument, "SpreadElement")) return UNKNOWN_VALUE;
|
|
52340
|
+
bindings.set(parameter.name, evaluateExpression(argument, state));
|
|
52341
|
+
}
|
|
52342
|
+
return evaluateExpression(returnedExpression, {
|
|
52343
|
+
...state,
|
|
52344
|
+
activeFunctions: new Set([...state.activeFunctions, localFunction]),
|
|
52345
|
+
bindings
|
|
52346
|
+
});
|
|
52347
|
+
};
|
|
52348
|
+
const comparatorProvesEmptyPropDoesNotBreakMemo = (comparatorExpression, propName, emptyLiteralKind, scopes) => {
|
|
52349
|
+
const comparator = resolveExactLocalFunction(comparatorExpression, scopes);
|
|
52350
|
+
if (!isFunctionLike$1(comparator) || comparator.async || comparator.generator) return false;
|
|
52351
|
+
const returnedExpression = getReturnedExpression(comparator);
|
|
52352
|
+
if (!returnedExpression) return false;
|
|
52353
|
+
const parameters = comparator.params ?? [];
|
|
52354
|
+
if (parameters.length !== 2 || !isNodeOfType(parameters[0], "Identifier") || !isNodeOfType(parameters[1], "Identifier")) return false;
|
|
52355
|
+
const bindings = new Map([[parameters[0].name, PREVIOUS_PROPS_VALUE], [parameters[1].name, NEXT_PROPS_VALUE]]);
|
|
52356
|
+
const evaluateComparator = (emptyReferencesAreEqual) => evaluateExpression(returnedExpression, {
|
|
52357
|
+
activeFunctions: new Set([comparator]),
|
|
52358
|
+
bindings,
|
|
52359
|
+
emptyLiteralKind,
|
|
52360
|
+
emptyReferencesAreEqual,
|
|
52361
|
+
propName,
|
|
52362
|
+
scopes
|
|
52363
|
+
});
|
|
52364
|
+
const distinctReferenceFormula = getBooleanFormula(evaluateComparator(false));
|
|
52365
|
+
const sharedReferenceFormula = getBooleanFormula(evaluateComparator(true));
|
|
52366
|
+
return Boolean(distinctReferenceFormula && sharedReferenceFormula && !couldStableTargetReferencePreventRender(distinctReferenceFormula, sharedReferenceFormula));
|
|
52367
|
+
};
|
|
52368
|
+
//#endregion
|
|
50969
52369
|
//#region src/plugin/rules/performance/rerender-memo-with-default-value.ts
|
|
50970
52370
|
const DEP_TAKING_HOOK_NAMES = new Set([
|
|
50971
52371
|
...HOOKS_WITH_DEPS,
|
|
@@ -51043,7 +52443,7 @@ const markCandidateIdentifier = (expression, candidateNames, shadowedNames, use,
|
|
|
51043
52443
|
if (!candidateNames.has(stripped.name)) return;
|
|
51044
52444
|
if (!into.has(stripped.name)) into.set(stripped.name, use);
|
|
51045
52445
|
};
|
|
51046
|
-
const collectIdentitySensitiveUses = (node, candidateNames, shadowedNames, memoRegistry, into) => {
|
|
52446
|
+
const collectIdentitySensitiveUses = (node, candidateNames, shadowedNames, memoRegistry, memoComparatorRegistry, defaultedBindings, context, into) => {
|
|
51047
52447
|
let innerShadowedNames = shadowedNames;
|
|
51048
52448
|
if (isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression")) {
|
|
51049
52449
|
const parameterNames = /* @__PURE__ */ new Set();
|
|
@@ -51058,12 +52458,21 @@ const collectIdentitySensitiveUses = (node, candidateNames, shadowedNames, memoR
|
|
|
51058
52458
|
if (isNodeOfType(node, "JSXOpeningElement")) {
|
|
51059
52459
|
const openingName = node.name;
|
|
51060
52460
|
const memoStatus = memoStatusForJsxOpeningName(memoRegistry, openingName);
|
|
51061
|
-
if (!isIntrinsicJsxElementName(openingName) && memoStatus !== "not-memoised")
|
|
51062
|
-
|
|
51063
|
-
|
|
51064
|
-
const
|
|
51065
|
-
|
|
51066
|
-
|
|
52461
|
+
if (!isIntrinsicJsxElementName(openingName) && memoStatus !== "not-memoised") {
|
|
52462
|
+
const comparatorDescriptor = isNodeOfType(openingName, "JSXIdentifier") ? memoComparatorRegistry.get(openingName.name) : void 0;
|
|
52463
|
+
const comparator = comparatorDescriptor && context.scopes.symbolFor(openingName)?.bindingIdentifier === comparatorDescriptor.bindingIdentifier ? comparatorDescriptor.comparator : void 0;
|
|
52464
|
+
for (const attribute of node.attributes ?? []) {
|
|
52465
|
+
if (!isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
52466
|
+
if (!attribute.value || !isNodeOfType(attribute.value, "JSXExpressionContainer")) continue;
|
|
52467
|
+
const attributeExpression = attribute.value.expression;
|
|
52468
|
+
if (!attributeExpression || attributeExpression.type === "JSXEmptyExpression") continue;
|
|
52469
|
+
const strippedAttributeExpression = stripParenExpression(attributeExpression);
|
|
52470
|
+
if (comparator && isNodeOfType(attribute.name, "JSXIdentifier") && isNodeOfType(strippedAttributeExpression, "Identifier") && !innerShadowedNames.has(strippedAttributeExpression.name)) {
|
|
52471
|
+
const binding = defaultedBindings.get(strippedAttributeExpression.name);
|
|
52472
|
+
if (binding && comparatorProvesEmptyPropDoesNotBreakMemo(comparator, attribute.name.name, binding.literalKind, context.scopes)) continue;
|
|
52473
|
+
}
|
|
52474
|
+
markCandidateIdentifier(attributeExpression, candidateNames, innerShadowedNames, "memoized-prop", into);
|
|
52475
|
+
}
|
|
51067
52476
|
}
|
|
51068
52477
|
}
|
|
51069
52478
|
const nodeRecord = node;
|
|
@@ -51071,8 +52480,8 @@ const collectIdentitySensitiveUses = (node, candidateNames, shadowedNames, memoR
|
|
|
51071
52480
|
if (key === "parent") continue;
|
|
51072
52481
|
const child = nodeRecord[key];
|
|
51073
52482
|
if (Array.isArray(child)) {
|
|
51074
|
-
for (const item of child) if (isAstNode(item)) collectIdentitySensitiveUses(item, candidateNames, innerShadowedNames, memoRegistry, into);
|
|
51075
|
-
} else if (isAstNode(child)) collectIdentitySensitiveUses(child, candidateNames, innerShadowedNames, memoRegistry, into);
|
|
52483
|
+
for (const item of child) if (isAstNode(item)) collectIdentitySensitiveUses(item, candidateNames, innerShadowedNames, memoRegistry, memoComparatorRegistry, defaultedBindings, context, into);
|
|
52484
|
+
} else if (isAstNode(child)) collectIdentitySensitiveUses(child, candidateNames, innerShadowedNames, memoRegistry, memoComparatorRegistry, defaultedBindings, context, into);
|
|
51076
52485
|
}
|
|
51077
52486
|
};
|
|
51078
52487
|
const buildMessage$2 = (literalKind, use) => {
|
|
@@ -51089,13 +52498,14 @@ const rerenderMemoWithDefaultValue = defineRule({
|
|
|
51089
52498
|
recommendation: "Move it to the top of the file: `const EMPTY_ITEMS: Item[] = []`, then use that as the default value",
|
|
51090
52499
|
create: (context) => {
|
|
51091
52500
|
let memoRegistry = /* @__PURE__ */ new Map();
|
|
52501
|
+
let memoComparatorRegistry = /* @__PURE__ */ new Map();
|
|
51092
52502
|
const checkComponentFunction = (functionNode) => {
|
|
51093
52503
|
if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return;
|
|
51094
52504
|
const defaultedBindings = collectDefaultedEmptyBindings(functionNode);
|
|
51095
52505
|
if (defaultedBindings.size === 0) return;
|
|
51096
52506
|
if (!functionNode.body) return;
|
|
51097
52507
|
const identitySensitiveUses = /* @__PURE__ */ new Map();
|
|
51098
|
-
collectIdentitySensitiveUses(functionNode.body, new Set(defaultedBindings.keys()), /* @__PURE__ */ new Set(), memoRegistry, identitySensitiveUses);
|
|
52508
|
+
collectIdentitySensitiveUses(functionNode.body, new Set(defaultedBindings.keys()), /* @__PURE__ */ new Set(), memoRegistry, memoComparatorRegistry, defaultedBindings, context, identitySensitiveUses);
|
|
51099
52509
|
for (const [bindingName, binding] of defaultedBindings) {
|
|
51100
52510
|
const use = identitySensitiveUses.get(bindingName);
|
|
51101
52511
|
if (!use) continue;
|
|
@@ -51108,6 +52518,7 @@ const rerenderMemoWithDefaultValue = defineRule({
|
|
|
51108
52518
|
return {
|
|
51109
52519
|
Program(node) {
|
|
51110
52520
|
memoRegistry = buildSameFileMemoRegistry(node);
|
|
52521
|
+
memoComparatorRegistry = buildSameFileMemoComparatorRegistry(node);
|
|
51111
52522
|
},
|
|
51112
52523
|
CallExpression(node) {
|
|
51113
52524
|
for (const finding of findForwardedFreshHookDependencies(node, context)) {
|
|
@@ -66052,6 +67463,7 @@ const CROSS_FILE_RULE_IDS = new Set([
|
|
|
66052
67463
|
"no-effect-with-fresh-deps",
|
|
66053
67464
|
"no-initialize-state",
|
|
66054
67465
|
"no-mutating-reducer-state",
|
|
67466
|
+
"only-export-components",
|
|
66055
67467
|
"no-unguarded-browser-global-in-render-or-hook-init",
|
|
66056
67468
|
"prefer-dynamic-import",
|
|
66057
67469
|
"rendering-hydration-mismatch-time",
|
|
@@ -66259,7 +67671,7 @@ const CROSS_FILE_DEPENDENCY_COLLECTORS = new Map([
|
|
|
66259
67671
|
* `CROSS_FILE_DEPENDENCY_COLLECTORS` (the core guard test enforces the
|
|
66260
67672
|
* partition), forcing a conscious classification.
|
|
66261
67673
|
*/
|
|
66262
|
-
const UNBOUNDED_CROSS_FILE_RULE_IDS =
|
|
67674
|
+
const UNBOUNDED_CROSS_FILE_RULE_IDS = new Set(["only-export-components"]);
|
|
66263
67675
|
/**
|
|
66264
67676
|
* Runs the collectors for `ruleIds` over one file and returns every
|
|
66265
67677
|
* filesystem probe they made — the file's cross-file dependency set.
|