oxlint-plugin-react-doctor 0.7.7-dev.6e5c240 → 0.7.7-dev.760a3b0
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 +581 -49
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4573,6 +4573,23 @@ const hasPossibleStaticPropertyWrite = (identifier, propertyName, scopes) => {
|
|
|
4573
4573
|
return writtenPropertyName === null || writtenPropertyName === propertyName;
|
|
4574
4574
|
}));
|
|
4575
4575
|
};
|
|
4576
|
+
const canExecuteBefore = (candidateNode, referenceNode, scopes) => {
|
|
4577
|
+
const candidateBoundary = findExecutionBoundary(candidateNode);
|
|
4578
|
+
const referenceBoundary = findExecutionBoundary(referenceNode);
|
|
4579
|
+
if (!candidateBoundary || !referenceBoundary) return true;
|
|
4580
|
+
if (candidateBoundary === referenceBoundary) return candidateNode.range[0] < referenceNode.range[0];
|
|
4581
|
+
if (!isFunctionLike$1(candidateBoundary)) return true;
|
|
4582
|
+
return isFunctionSynchronouslyInvokedBefore(candidateBoundary, referenceNode, scopes);
|
|
4583
|
+
};
|
|
4584
|
+
const hasPossibleStaticPropertyWriteBefore = (identifier, propertyName, referenceNode, scopes) => {
|
|
4585
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
4586
|
+
return getPotentiallyAliasedSymbols(identifier, scopes).some((symbol) => symbol.references.some((reference) => {
|
|
4587
|
+
const writeTarget = getMemberWriteTarget(reference.identifier);
|
|
4588
|
+
if (!writeTarget || !canExecuteBefore(writeTarget, referenceNode, scopes)) return false;
|
|
4589
|
+
const writtenPropertyName = getResolvedStaticPropertyName(writeTarget, scopes);
|
|
4590
|
+
return writtenPropertyName === null || writtenPropertyName === propertyName;
|
|
4591
|
+
}));
|
|
4592
|
+
};
|
|
4576
4593
|
const hasPossibleStaticPropertyMutationOrEscape = (identifier, propertyName, scopes) => {
|
|
4577
4594
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
4578
4595
|
if (hasPossibleStaticPropertyWrite(identifier, propertyName, scopes)) return true;
|
|
@@ -14609,7 +14626,7 @@ const buildAsyncEffectMessage = (hookName) => `\`${hookName}\` was given an asyn
|
|
|
14609
14626
|
const buildUnknownCallbackMessage = (hookName) => `\`${hookName}\`'s callback is defined elsewhere, so dependencies can't be checked and stale values can slip through.`;
|
|
14610
14627
|
const buildUnstableDepMessage = (hookName, depName) => `\`${depName}\` is rebuilt every render, so \`${hookName}\` runs every time.`;
|
|
14611
14628
|
const buildForwardedUnstableDepMessage = (depName) => `\`${depName}\` is rebuilt every render and reaches a Hook dependency inside this custom Hook.`;
|
|
14612
|
-
const buildSetStateWithoutDepsMessage = (hookName, setterName) => `\`${hookName}\` calls \`${setterName}\` with no dependency array, so it can
|
|
14629
|
+
const buildSetStateWithoutDepsMessage = (hookName, setterName) => `\`${hookName}\` calls \`${setterName}\` with a value that can change on every render and no dependency array, so it can keep triggering renders.`;
|
|
14613
14630
|
const buildRefCleanupMessage = (depName) => `Your cleanup may read the wrong node since the ref \`${depName}\` can change before it runs.`;
|
|
14614
14631
|
const buildAssignmentMessage = (name) => `Assigning to \`${name}\` inside a hook is thrown away after each render, so the next render reads the old value.`;
|
|
14615
14632
|
//#endregion
|
|
@@ -15369,26 +15386,125 @@ const isExtraDepAllowedForHook = (hookName, node, scopes) => {
|
|
|
15369
15386
|
};
|
|
15370
15387
|
const hasDirectIdentifierDeclarator = (symbol) => isNodeOfType(symbol.declarationNode, "VariableDeclarator") && isNodeOfType(symbol.declarationNode.id, "Identifier") || isNodeOfType(symbol.declarationNode, "ClassDeclaration");
|
|
15371
15388
|
const isFunctionValueSymbol = (symbol) => getFunctionValueNode(symbol) !== null;
|
|
15372
|
-
const
|
|
15373
|
-
|
|
15374
|
-
|
|
15389
|
+
const FRESH_STATE_CONSTRUCTOR_NAMES = new Set([
|
|
15390
|
+
"Array",
|
|
15391
|
+
"Date",
|
|
15392
|
+
"Error",
|
|
15393
|
+
"Map",
|
|
15394
|
+
"Object",
|
|
15395
|
+
"RegExp",
|
|
15396
|
+
"Set",
|
|
15397
|
+
"WeakMap",
|
|
15398
|
+
"WeakSet"
|
|
15399
|
+
]);
|
|
15400
|
+
const getBindingIdentifier = (node) => {
|
|
15401
|
+
if (!node) return null;
|
|
15402
|
+
const candidate = isNodeOfType(node, "AssignmentPattern") ? node.left : node;
|
|
15403
|
+
return isNodeOfType(candidate, "Identifier") ? candidate : null;
|
|
15375
15404
|
};
|
|
15376
|
-
const
|
|
15377
|
-
const
|
|
15378
|
-
|
|
15379
|
-
const
|
|
15380
|
-
|
|
15381
|
-
|
|
15382
|
-
|
|
15383
|
-
|
|
15384
|
-
|
|
15385
|
-
|
|
15386
|
-
|
|
15405
|
+
const resolveStateSetterSymbol = (identifier, scopes) => {
|
|
15406
|
+
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
15407
|
+
let symbol = scopes.symbolFor(identifier);
|
|
15408
|
+
while (symbol?.kind === "const" && isNodeOfType(symbol.declarationNode, "VariableDeclarator") && symbol.declarationNode.id === symbol.bindingIdentifier && symbol.initializer) {
|
|
15409
|
+
if (visitedSymbolIds.has(symbol.id)) return null;
|
|
15410
|
+
visitedSymbolIds.add(symbol.id);
|
|
15411
|
+
const initializer = unwrapExpression$3(symbol.initializer);
|
|
15412
|
+
if (!isNodeOfType(initializer, "Identifier")) return symbol;
|
|
15413
|
+
symbol = scopes.symbolFor(initializer);
|
|
15414
|
+
}
|
|
15415
|
+
return symbol;
|
|
15416
|
+
};
|
|
15417
|
+
const getStateSetterDescriptor = (identifier, scopes) => {
|
|
15418
|
+
const setterSymbol = resolveStateSetterSymbol(identifier, scopes);
|
|
15419
|
+
if (!setterSymbol || !isNodeOfType(setterSymbol.declarationNode, "VariableDeclarator")) return null;
|
|
15420
|
+
const declarator = setterSymbol.declarationNode;
|
|
15421
|
+
if (!isNodeOfType(declarator.id, "ArrayPattern")) return null;
|
|
15422
|
+
const setterElement = declarator.id.elements?.[1];
|
|
15423
|
+
const setterBinding = isAstNode(setterElement) ? getBindingIdentifier(setterElement) : null;
|
|
15424
|
+
if (!setterBinding || setterBinding !== setterSymbol.bindingIdentifier) return null;
|
|
15425
|
+
const initializer = declarator.init ? unwrapExpression$3(declarator.init) : null;
|
|
15426
|
+
if (!initializer || !isNodeOfType(initializer, "CallExpression") || !isReactApiCall(initializer, "useState", scopes, {
|
|
15427
|
+
allowGlobalReactNamespace: true,
|
|
15428
|
+
allowUnboundBareCalls: true,
|
|
15429
|
+
resolveNamedAliases: true
|
|
15430
|
+
})) return null;
|
|
15431
|
+
const stateElement = declarator.id.elements?.[0];
|
|
15432
|
+
const stateBinding = isAstNode(stateElement) ? getBindingIdentifier(stateElement) : null;
|
|
15433
|
+
return {
|
|
15434
|
+
setterSymbol,
|
|
15435
|
+
stateSymbol: stateBinding ? scopes.symbolFor(stateBinding) : null
|
|
15436
|
+
};
|
|
15437
|
+
};
|
|
15438
|
+
const expressionReadsSymbol = (node, symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
15439
|
+
if (!symbol) return false;
|
|
15440
|
+
const candidate = unwrapExpression$3(node);
|
|
15441
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
15442
|
+
const reference = scopes.referenceFor(candidate);
|
|
15443
|
+
if (reference?.flag !== "write" && reference?.resolvedSymbol?.id === symbol.id) return true;
|
|
15444
|
+
const candidateSymbol = reference?.resolvedSymbol;
|
|
15445
|
+
if (candidateSymbol?.kind === "const" && candidateSymbol.initializer && !isOutsideAllFunctions(candidateSymbol) && !visitedSymbolIds.has(candidateSymbol.id)) {
|
|
15446
|
+
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
15447
|
+
nextVisitedSymbolIds.add(candidateSymbol.id);
|
|
15448
|
+
return expressionReadsSymbol(candidateSymbol.initializer, symbol, scopes, nextVisitedSymbolIds);
|
|
15387
15449
|
}
|
|
15450
|
+
return false;
|
|
15451
|
+
}
|
|
15452
|
+
const record = candidate;
|
|
15453
|
+
for (const key of Object.keys(record)) {
|
|
15454
|
+
if (key === "parent") continue;
|
|
15455
|
+
const child = record[key];
|
|
15456
|
+
if (Array.isArray(child)) {
|
|
15457
|
+
if (child.some((item) => isAstNode(item) && expressionReadsSymbol(item, symbol, scopes))) return true;
|
|
15458
|
+
} else if (isAstNode(child) && expressionReadsSymbol(child, symbol, scopes)) return true;
|
|
15388
15459
|
}
|
|
15389
|
-
|
|
15390
|
-
|
|
15391
|
-
|
|
15460
|
+
return false;
|
|
15461
|
+
};
|
|
15462
|
+
const isGuaranteedFreshStateValue = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
15463
|
+
const candidate = unwrapExpression$3(node);
|
|
15464
|
+
if (isNodeOfType(candidate, "ObjectExpression") || isNodeOfType(candidate, "ArrayExpression") || isNodeOfType(candidate, "JSXElement") || isNodeOfType(candidate, "JSXFragment") || isRegExpLiteral(candidate)) return true;
|
|
15465
|
+
if (isNodeOfType(candidate, "SequenceExpression")) {
|
|
15466
|
+
const finalExpression = candidate.expressions.at(-1);
|
|
15467
|
+
return Boolean(finalExpression && isGuaranteedFreshStateValue(finalExpression, scopes));
|
|
15468
|
+
}
|
|
15469
|
+
if (isNodeOfType(candidate, "ConditionalExpression")) return isGuaranteedFreshStateValue(candidate.consequent, scopes, visitedSymbolIds) && isGuaranteedFreshStateValue(candidate.alternate, scopes, visitedSymbolIds);
|
|
15470
|
+
if (isNodeOfType(candidate, "LogicalExpression")) return isGuaranteedFreshStateValue(candidate.left, scopes, visitedSymbolIds) && isGuaranteedFreshStateValue(candidate.right, scopes, visitedSymbolIds);
|
|
15471
|
+
if (isNodeOfType(candidate, "CallExpression")) {
|
|
15472
|
+
const callee = unwrapExpression$3(candidate.callee);
|
|
15473
|
+
return isNodeOfType(callee, "Identifier") && scopes.isGlobalReference(callee) && (callee.name === "Array" || callee.name === "Object") && (callee.name === "Array" || candidate.arguments.length === 0);
|
|
15474
|
+
}
|
|
15475
|
+
if (isNodeOfType(candidate, "NewExpression")) {
|
|
15476
|
+
const callee = unwrapExpression$3(candidate.callee);
|
|
15477
|
+
return isNodeOfType(callee, "Identifier") && scopes.isGlobalReference(callee) && FRESH_STATE_CONSTRUCTOR_NAMES.has(callee.name) && (callee.name !== "Object" || candidate.arguments.length === 0);
|
|
15478
|
+
}
|
|
15479
|
+
if (!isNodeOfType(candidate, "Identifier")) return false;
|
|
15480
|
+
const symbol = scopes.symbolFor(candidate);
|
|
15481
|
+
if (!symbol || symbol.kind !== "const" || !symbol.initializer || isOutsideAllFunctions(symbol) || visitedSymbolIds.has(symbol.id)) return false;
|
|
15482
|
+
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
15483
|
+
nextVisitedSymbolIds.add(symbol.id);
|
|
15484
|
+
return isGuaranteedFreshStateValue(symbol.initializer, scopes, nextVisitedSymbolIds);
|
|
15485
|
+
};
|
|
15486
|
+
const isNonZeroLiteral = (node) => {
|
|
15487
|
+
const candidate = unwrapExpression$3(node);
|
|
15488
|
+
return isNodeOfType(candidate, "Literal") && (typeof candidate.value === "number" && candidate.value !== 0 || typeof candidate.value === "bigint" && candidate.value !== 0n || typeof candidate.value === "string" && candidate.value.length > 0);
|
|
15489
|
+
};
|
|
15490
|
+
const isProvablyChangingExpression = (node, previousValueSymbol, scopes) => {
|
|
15491
|
+
const candidate = unwrapExpression$3(node);
|
|
15492
|
+
if (isGuaranteedFreshStateValue(candidate, scopes)) return true;
|
|
15493
|
+
if (isNodeOfType(candidate, "ConditionalExpression")) {
|
|
15494
|
+
const isConsequentChanging = isProvablyChangingExpression(candidate.consequent, previousValueSymbol, scopes);
|
|
15495
|
+
const isAlternateChanging = isProvablyChangingExpression(candidate.alternate, previousValueSymbol, scopes);
|
|
15496
|
+
return expressionReadsSymbol(candidate.test, previousValueSymbol, scopes) ? isConsequentChanging && isAlternateChanging : isConsequentChanging || isAlternateChanging;
|
|
15497
|
+
}
|
|
15498
|
+
if (!expressionReadsSymbol(candidate, previousValueSymbol, scopes)) return false;
|
|
15499
|
+
if (isNodeOfType(candidate, "UnaryExpression")) return candidate.operator === "!";
|
|
15500
|
+
if (isNodeOfType(candidate, "UpdateExpression")) return true;
|
|
15501
|
+
if (!isNodeOfType(candidate, "BinaryExpression")) return false;
|
|
15502
|
+
return (candidate.operator === "+" || candidate.operator === "-") && expressionReadsSymbol(candidate.left, previousValueSymbol, scopes) && isNonZeroLiteral(candidate.right);
|
|
15503
|
+
};
|
|
15504
|
+
const isFreshEqualityGuardUpdater = (node, previousValueSymbol, scopes) => {
|
|
15505
|
+
const candidate = unwrapExpression$3(node);
|
|
15506
|
+
if (!isNodeOfType(candidate, "ConditionalExpression")) return false;
|
|
15507
|
+
const test = unwrapExpression$3(candidate.test);
|
|
15392
15508
|
if (!isNodeOfType(test, "BinaryExpression") || ![
|
|
15393
15509
|
"===",
|
|
15394
15510
|
"!==",
|
|
@@ -15396,38 +15512,50 @@ const isConvergingFunctionalUpdater = (node, scopes) => {
|
|
|
15396
15512
|
"!="
|
|
15397
15513
|
].includes(test.operator)) return false;
|
|
15398
15514
|
const isPreviousValue = (expression) => {
|
|
15399
|
-
const
|
|
15400
|
-
return isNodeOfType(
|
|
15515
|
+
const expressionCandidate = unwrapExpression$3(expression);
|
|
15516
|
+
return isNodeOfType(expressionCandidate, "Identifier") && scopes.symbolFor(expressionCandidate)?.id === previousValueSymbol?.id;
|
|
15401
15517
|
};
|
|
15402
15518
|
let comparedValue = null;
|
|
15403
15519
|
if (isPreviousValue(test.left)) comparedValue = test.right;
|
|
15404
|
-
|
|
15405
|
-
if (!comparedValue) return false;
|
|
15406
|
-
|
|
15407
|
-
|
|
15408
|
-
const
|
|
15409
|
-
|
|
15410
|
-
|
|
15411
|
-
if (isNodeOfType(candidate, "Literal") && isNodeOfType(compared, "Literal")) return candidate.value === compared.value;
|
|
15412
|
-
return false;
|
|
15520
|
+
if (isPreviousValue(test.right)) comparedValue = test.left;
|
|
15521
|
+
if (!comparedValue || !isPotentiallyFreshComparedValue(comparedValue, scopes)) return false;
|
|
15522
|
+
const isComparedValue = (expression) => {
|
|
15523
|
+
const expressionCandidate = unwrapExpression$3(expression);
|
|
15524
|
+
const comparedCandidate = unwrapExpression$3(comparedValue);
|
|
15525
|
+
if (isNodeOfType(expressionCandidate, "Identifier")) return isNodeOfType(comparedCandidate, "Identifier") && scopes.symbolFor(expressionCandidate)?.id === scopes.symbolFor(comparedCandidate)?.id;
|
|
15526
|
+
return isNodeOfType(expressionCandidate, "Literal") && isNodeOfType(comparedCandidate, "Literal") && expressionCandidate.value === comparedCandidate.value;
|
|
15413
15527
|
};
|
|
15414
|
-
return test.operator === "===" || test.operator === "==" ? isPreviousValue(
|
|
15415
|
-
};
|
|
15416
|
-
const isGuardedStableSetterCall = (identifier, symbol, scopes) => {
|
|
15417
|
-
const parent = identifier.parent;
|
|
15418
|
-
if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier || !isStableSetterLikeSymbol(symbol, scopes)) return false;
|
|
15419
|
-
const writtenValue = parent.arguments?.[0];
|
|
15420
|
-
return Boolean(writtenValue && isConvergingFunctionalUpdater(writtenValue, scopes));
|
|
15528
|
+
return test.operator === "===" || test.operator === "==" ? isPreviousValue(candidate.consequent) && isComparedValue(candidate.alternate) : isComparedValue(candidate.consequent) && isPreviousValue(candidate.alternate);
|
|
15421
15529
|
};
|
|
15422
|
-
const
|
|
15530
|
+
const isProvablyChangingFunctionalUpdater = (node, scopes) => {
|
|
15531
|
+
const updater = unwrapExpression$3(node);
|
|
15532
|
+
if (!isNodeOfType(updater, "ArrowFunctionExpression") && !isNodeOfType(updater, "FunctionExpression")) return false;
|
|
15533
|
+
const previousValueParameter = updater.params?.[0];
|
|
15534
|
+
if (!previousValueParameter || !isNodeOfType(previousValueParameter, "Identifier")) return false;
|
|
15535
|
+
const previousValueSymbol = scopes.symbolFor(previousValueParameter);
|
|
15536
|
+
if (!isNodeOfType(updater.body, "BlockStatement")) return isFreshEqualityGuardUpdater(updater.body, previousValueSymbol, scopes) || isProvablyChangingExpression(updater.body, previousValueSymbol, scopes);
|
|
15537
|
+
if (updater.body.body.length !== 1) return false;
|
|
15538
|
+
const soleStatement = updater.body.body[0];
|
|
15539
|
+
if (!isNodeOfType(soleStatement, "ReturnStatement") || !isAstNode(soleStatement.argument)) return false;
|
|
15540
|
+
return isFreshEqualityGuardUpdater(soleStatement.argument, previousValueSymbol, scopes) || isProvablyChangingExpression(soleStatement.argument, previousValueSymbol, scopes);
|
|
15541
|
+
};
|
|
15542
|
+
const isProvablyRenderChangingSetterCall = (identifier, scopes) => {
|
|
15543
|
+
const setterCall = identifier.parent;
|
|
15544
|
+
if (!setterCall || !isNodeOfType(setterCall, "CallExpression") || setterCall.callee !== identifier) return false;
|
|
15545
|
+
const descriptor = getStateSetterDescriptor(identifier, scopes);
|
|
15546
|
+
const writtenValue = setterCall.arguments?.[0];
|
|
15547
|
+
if (!descriptor || !writtenValue || !isAstNode(writtenValue)) return false;
|
|
15548
|
+
return isProvablyChangingFunctionalUpdater(writtenValue, scopes) ? true : isProvablyChangingExpression(writtenValue, descriptor.stateSymbol, scopes);
|
|
15549
|
+
};
|
|
15550
|
+
const findRenderChangingStateSetterName = (node, scopes) => {
|
|
15423
15551
|
let setterName = null;
|
|
15424
15552
|
const visit = (current) => {
|
|
15425
15553
|
if (setterName) return;
|
|
15426
15554
|
if (current !== node && (isNodeOfType(current, "FunctionDeclaration") || isNodeOfType(current, "FunctionExpression") || isNodeOfType(current, "ArrowFunctionExpression"))) return;
|
|
15427
15555
|
if (isNodeOfType(current, "Identifier")) {
|
|
15428
|
-
const
|
|
15429
|
-
if (
|
|
15430
|
-
setterName =
|
|
15556
|
+
const descriptor = getStateSetterDescriptor(current, scopes);
|
|
15557
|
+
if (descriptor && isProvablyRenderChangingSetterCall(current, scopes)) {
|
|
15558
|
+
setterName = descriptor.setterSymbol.name;
|
|
15431
15559
|
return;
|
|
15432
15560
|
}
|
|
15433
15561
|
}
|
|
@@ -15754,7 +15882,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
15754
15882
|
}
|
|
15755
15883
|
if (!depsArgumentRaw) {
|
|
15756
15884
|
if (callbackToAnalyze && EFFECT_HOOKS_ALLOWING_EXTRA_REACTIVE_DEPS.has(hookName)) {
|
|
15757
|
-
const setterName =
|
|
15885
|
+
const setterName = findRenderChangingStateSetterName(callbackToAnalyze, context.scopes);
|
|
15758
15886
|
if (setterName) {
|
|
15759
15887
|
context.report({
|
|
15760
15888
|
node: callbackToAnalyze,
|
|
@@ -20147,14 +20275,14 @@ const isIndexOfResultUsedAsMembershipTest = (node) => {
|
|
|
20147
20275
|
if (isNegativeOneLiteral(otherOperand)) return true;
|
|
20148
20276
|
return isZeroLiteral(otherOperand) && (parent.operator === ">=" || parent.operator === "<");
|
|
20149
20277
|
};
|
|
20150
|
-
const getTypeAnnotation = (node) => {
|
|
20278
|
+
const getTypeAnnotation$1 = (node) => {
|
|
20151
20279
|
if (!node || !("typeAnnotation" in node)) return null;
|
|
20152
20280
|
const annotation = node.typeAnnotation;
|
|
20153
20281
|
if (!annotation || !isNodeOfType(annotation, "TSTypeAnnotation")) return null;
|
|
20154
20282
|
return annotation.typeAnnotation;
|
|
20155
20283
|
};
|
|
20156
20284
|
const getDeclaredPropertyType = (members, propertyName) => {
|
|
20157
|
-
for (const member of members) if (isNodeOfType(member, "TSPropertySignature") && isNodeOfType(member.key, "Identifier") && member.key.name === propertyName) return getTypeAnnotation(member);
|
|
20285
|
+
for (const member of members) if (isNodeOfType(member, "TSPropertySignature") && isNodeOfType(member.key, "Identifier") && member.key.name === propertyName) return getTypeAnnotation$1(member);
|
|
20158
20286
|
return null;
|
|
20159
20287
|
};
|
|
20160
20288
|
const getArrayElementType = (typeNode) => {
|
|
@@ -20173,7 +20301,7 @@ const getDestructuredDeclaredType = (identifier) => {
|
|
|
20173
20301
|
const property = binding.bindingIdentifier.parent;
|
|
20174
20302
|
const objectPattern = property?.parent;
|
|
20175
20303
|
if (!isNodeOfType(property, "Property") || !isNodeOfType(property.key, "Identifier") || !isNodeOfType(objectPattern, "ObjectPattern")) return null;
|
|
20176
|
-
const propsType = getTypeAnnotation(objectPattern);
|
|
20304
|
+
const propsType = getTypeAnnotation$1(objectPattern);
|
|
20177
20305
|
if (!propsType) return null;
|
|
20178
20306
|
if (isNodeOfType(propsType, "TSTypeLiteral")) return getDeclaredPropertyType(propsType.members ?? [], property.key.name);
|
|
20179
20307
|
if (!isNodeOfType(propsType, "TSTypeReference") || !isNodeOfType(propsType.typeName, "Identifier")) return null;
|
|
@@ -20190,7 +20318,7 @@ const getIdentifierDeclaredType = (identifier, visitedBindingIdentifiers = /* @_
|
|
|
20190
20318
|
const binding = findVariableInitializer(identifier, identifier.name);
|
|
20191
20319
|
if (!binding || visitedBindingIdentifiers.has(binding.bindingIdentifier)) return null;
|
|
20192
20320
|
visitedBindingIdentifiers.add(binding.bindingIdentifier);
|
|
20193
|
-
const directType = getTypeAnnotation(binding.bindingIdentifier);
|
|
20321
|
+
const directType = getTypeAnnotation$1(binding.bindingIdentifier);
|
|
20194
20322
|
if (directType) return directType;
|
|
20195
20323
|
const initializer = binding.initializer;
|
|
20196
20324
|
if (isNodeOfType(initializer, "TSAsExpression") || isNodeOfType(initializer, "TSTypeAssertion") || isNodeOfType(initializer, "TSSatisfiesExpression")) return initializer.typeAnnotation;
|
|
@@ -27355,6 +27483,211 @@ const computeExternallyDriven = (analysis, declarator) => {
|
|
|
27355
27483
|
return hasDeferredCallSite;
|
|
27356
27484
|
};
|
|
27357
27485
|
//#endregion
|
|
27486
|
+
//#region src/plugin/rules/state-and-effects/utils/effect/is-proven-native-read-method.ts
|
|
27487
|
+
const ARRAY_READ_METHOD_NAMES$1 = new Set([
|
|
27488
|
+
"at",
|
|
27489
|
+
"concat",
|
|
27490
|
+
"entries",
|
|
27491
|
+
"every",
|
|
27492
|
+
"filter",
|
|
27493
|
+
"find",
|
|
27494
|
+
"findIndex",
|
|
27495
|
+
"findLast",
|
|
27496
|
+
"findLastIndex",
|
|
27497
|
+
"flat",
|
|
27498
|
+
"flatMap",
|
|
27499
|
+
"forEach",
|
|
27500
|
+
"includes",
|
|
27501
|
+
"indexOf",
|
|
27502
|
+
"join",
|
|
27503
|
+
"keys",
|
|
27504
|
+
"lastIndexOf",
|
|
27505
|
+
"map",
|
|
27506
|
+
"reduce",
|
|
27507
|
+
"reduceRight",
|
|
27508
|
+
"slice",
|
|
27509
|
+
"some",
|
|
27510
|
+
"toLocaleString",
|
|
27511
|
+
"toReversed",
|
|
27512
|
+
"toSorted",
|
|
27513
|
+
"toSpliced",
|
|
27514
|
+
"toString",
|
|
27515
|
+
"values",
|
|
27516
|
+
"with"
|
|
27517
|
+
]);
|
|
27518
|
+
const MAP_READ_METHOD_NAMES = new Set([
|
|
27519
|
+
"entries",
|
|
27520
|
+
"forEach",
|
|
27521
|
+
"get",
|
|
27522
|
+
"has",
|
|
27523
|
+
"keys",
|
|
27524
|
+
"values"
|
|
27525
|
+
]);
|
|
27526
|
+
const SET_READ_METHOD_NAMES = new Set([
|
|
27527
|
+
"difference",
|
|
27528
|
+
"entries",
|
|
27529
|
+
"forEach",
|
|
27530
|
+
"has",
|
|
27531
|
+
"intersection",
|
|
27532
|
+
"isDisjointFrom",
|
|
27533
|
+
"isSubsetOf",
|
|
27534
|
+
"isSupersetOf",
|
|
27535
|
+
"keys",
|
|
27536
|
+
"symmetricDifference",
|
|
27537
|
+
"union",
|
|
27538
|
+
"values"
|
|
27539
|
+
]);
|
|
27540
|
+
const FUNCTION_READ_METHOD_NAMES = new Set(["bind"]);
|
|
27541
|
+
const PROMISE_READ_METHOD_NAMES = new Set([
|
|
27542
|
+
"catch",
|
|
27543
|
+
"finally",
|
|
27544
|
+
"then"
|
|
27545
|
+
]);
|
|
27546
|
+
const STRING_READ_METHOD_NAMES$1 = new Set([
|
|
27547
|
+
"at",
|
|
27548
|
+
"charAt",
|
|
27549
|
+
"charCodeAt",
|
|
27550
|
+
"codePointAt",
|
|
27551
|
+
"concat",
|
|
27552
|
+
"endsWith",
|
|
27553
|
+
"includes",
|
|
27554
|
+
"indexOf",
|
|
27555
|
+
"isWellFormed",
|
|
27556
|
+
"lastIndexOf",
|
|
27557
|
+
"localeCompare",
|
|
27558
|
+
"match",
|
|
27559
|
+
"matchAll",
|
|
27560
|
+
"normalize",
|
|
27561
|
+
"padEnd",
|
|
27562
|
+
"padStart",
|
|
27563
|
+
"repeat",
|
|
27564
|
+
"replace",
|
|
27565
|
+
"replaceAll",
|
|
27566
|
+
"search",
|
|
27567
|
+
"slice",
|
|
27568
|
+
"split",
|
|
27569
|
+
"startsWith",
|
|
27570
|
+
"substring",
|
|
27571
|
+
"toLocaleLowerCase",
|
|
27572
|
+
"toLocaleUpperCase",
|
|
27573
|
+
"toLowerCase",
|
|
27574
|
+
"toString",
|
|
27575
|
+
"toUpperCase",
|
|
27576
|
+
"toWellFormed",
|
|
27577
|
+
"trim",
|
|
27578
|
+
"trimEnd",
|
|
27579
|
+
"trimStart",
|
|
27580
|
+
"valueOf"
|
|
27581
|
+
]);
|
|
27582
|
+
const NATIVE_TYPE_NAMES = new Set([
|
|
27583
|
+
"Array",
|
|
27584
|
+
"Map",
|
|
27585
|
+
"Promise",
|
|
27586
|
+
"PromiseLike",
|
|
27587
|
+
"ReadonlyArray",
|
|
27588
|
+
"ReadonlyMap",
|
|
27589
|
+
"ReadonlySet",
|
|
27590
|
+
"Set"
|
|
27591
|
+
]);
|
|
27592
|
+
const nativeTypeDeclarationsByProgram = /* @__PURE__ */ new WeakMap();
|
|
27593
|
+
const shadowedNativeTypeNamesByBinding = /* @__PURE__ */ new WeakMap();
|
|
27594
|
+
const isWithinNode = (node, ancestor) => {
|
|
27595
|
+
let current = node;
|
|
27596
|
+
while (current) {
|
|
27597
|
+
if (current === ancestor) return true;
|
|
27598
|
+
current = current.parent;
|
|
27599
|
+
}
|
|
27600
|
+
return false;
|
|
27601
|
+
};
|
|
27602
|
+
const findTypeDeclarationScope = (declaration) => {
|
|
27603
|
+
let current = declaration.parent;
|
|
27604
|
+
while (current) {
|
|
27605
|
+
if (isNodeOfType(current, "Program") || isNodeOfType(current, "BlockStatement") || isNodeOfType(current, "TSModuleBlock") || isNodeOfType(current, "StaticBlock")) return current;
|
|
27606
|
+
current = current.parent;
|
|
27607
|
+
}
|
|
27608
|
+
return null;
|
|
27609
|
+
};
|
|
27610
|
+
const getDeclaredTypeName = (declaration) => {
|
|
27611
|
+
if (!isNodeOfType(declaration, "ClassDeclaration") && !isNodeOfType(declaration, "TSEnumDeclaration") && !isNodeOfType(declaration, "TSImportEqualsDeclaration") && !isNodeOfType(declaration, "TSInterfaceDeclaration") && !isNodeOfType(declaration, "TSModuleDeclaration") && !isNodeOfType(declaration, "TSTypeAliasDeclaration")) return null;
|
|
27612
|
+
return declaration.id && isNodeOfType(declaration.id, "Identifier") ? declaration.id.name : null;
|
|
27613
|
+
};
|
|
27614
|
+
const getNativeTypeDeclarations = (program) => {
|
|
27615
|
+
const cachedDeclarations = nativeTypeDeclarationsByProgram.get(program);
|
|
27616
|
+
if (cachedDeclarations) return cachedDeclarations;
|
|
27617
|
+
const declarations = [];
|
|
27618
|
+
walkAst(program, (candidate) => {
|
|
27619
|
+
if (isNodeOfType(candidate, "ImportDeclaration")) {
|
|
27620
|
+
for (const specifier of candidate.specifiers) if (NATIVE_TYPE_NAMES.has(specifier.local.name)) declarations.push({
|
|
27621
|
+
name: specifier.local.name,
|
|
27622
|
+
scope: program
|
|
27623
|
+
});
|
|
27624
|
+
return;
|
|
27625
|
+
}
|
|
27626
|
+
const declaredTypeName = getDeclaredTypeName(candidate);
|
|
27627
|
+
if (!declaredTypeName || !NATIVE_TYPE_NAMES.has(declaredTypeName)) return;
|
|
27628
|
+
const declarationScope = findTypeDeclarationScope(candidate);
|
|
27629
|
+
if (declarationScope) declarations.push({
|
|
27630
|
+
name: declaredTypeName,
|
|
27631
|
+
scope: declarationScope
|
|
27632
|
+
});
|
|
27633
|
+
});
|
|
27634
|
+
nativeTypeDeclarationsByProgram.set(program, declarations);
|
|
27635
|
+
return declarations;
|
|
27636
|
+
};
|
|
27637
|
+
const getShadowedNativeTypeNames = (binding) => {
|
|
27638
|
+
const cachedNames = shadowedNativeTypeNamesByBinding.get(binding);
|
|
27639
|
+
if (cachedNames) return cachedNames;
|
|
27640
|
+
const shadowedNames = /* @__PURE__ */ new Set();
|
|
27641
|
+
for (const typeName of NATIVE_TYPE_NAMES) if (hasEnclosingTypeParameterNamed(binding, typeName)) shadowedNames.add(typeName);
|
|
27642
|
+
const program = findProgramRoot(binding);
|
|
27643
|
+
if (program) {
|
|
27644
|
+
for (const declaration of getNativeTypeDeclarations(program)) if (isWithinNode(binding, declaration.scope)) shadowedNames.add(declaration.name);
|
|
27645
|
+
}
|
|
27646
|
+
shadowedNativeTypeNamesByBinding.set(binding, shadowedNames);
|
|
27647
|
+
return shadowedNames;
|
|
27648
|
+
};
|
|
27649
|
+
const getParameterBinding = (definitionName) => {
|
|
27650
|
+
if (isNodeOfType(definitionName, "Identifier")) return definitionName;
|
|
27651
|
+
if (!isNodeOfType(definitionName, "AssignmentPattern")) return null;
|
|
27652
|
+
return isNodeOfType(definitionName.left, "Identifier") ? definitionName.left : null;
|
|
27653
|
+
};
|
|
27654
|
+
const getTypeAnnotation = (binding) => {
|
|
27655
|
+
if (!isNodeOfType(binding, "Identifier")) return null;
|
|
27656
|
+
const annotation = binding.typeAnnotation;
|
|
27657
|
+
return annotation && isNodeOfType(annotation, "TSTypeAnnotation") ? annotation.typeAnnotation : null;
|
|
27658
|
+
};
|
|
27659
|
+
const isNullishType = (typeNode) => isNodeOfType(typeNode, "TSNullKeyword") || isNodeOfType(typeNode, "TSUndefinedKeyword");
|
|
27660
|
+
const isNativeReadMethod = (typeNode, methodName, shadowedTypeNames) => {
|
|
27661
|
+
if (isNodeOfType(typeNode, "TSArrayType") || isNodeOfType(typeNode, "TSTupleType")) return ARRAY_READ_METHOD_NAMES$1.has(methodName);
|
|
27662
|
+
if (isNodeOfType(typeNode, "TSStringKeyword")) return STRING_READ_METHOD_NAMES$1.has(methodName);
|
|
27663
|
+
if (isNodeOfType(typeNode, "TSFunctionType")) return FUNCTION_READ_METHOD_NAMES.has(methodName);
|
|
27664
|
+
if (isNodeOfType(typeNode, "TSLiteralType") && isNodeOfType(typeNode.literal, "Literal") && typeof typeNode.literal.value === "string") return STRING_READ_METHOD_NAMES$1.has(methodName);
|
|
27665
|
+
if (isNodeOfType(typeNode, "TSTypeOperator") && typeNode.operator === "readonly" && typeNode.typeAnnotation) return isNativeReadMethod(typeNode.typeAnnotation, methodName, shadowedTypeNames);
|
|
27666
|
+
if (isNodeOfType(typeNode, "TSUnionType")) {
|
|
27667
|
+
let hasNonNullishMember = false;
|
|
27668
|
+
for (const memberType of typeNode.types) {
|
|
27669
|
+
if (isNullishType(memberType)) continue;
|
|
27670
|
+
hasNonNullishMember = true;
|
|
27671
|
+
if (!isNativeReadMethod(memberType, methodName, shadowedTypeNames)) return false;
|
|
27672
|
+
}
|
|
27673
|
+
return hasNonNullishMember;
|
|
27674
|
+
}
|
|
27675
|
+
if (!isNodeOfType(typeNode, "TSTypeReference") || !isNodeOfType(typeNode.typeName, "Identifier")) return false;
|
|
27676
|
+
if (shadowedTypeNames.has(typeNode.typeName.name)) return false;
|
|
27677
|
+
if (typeNode.typeName.name === "Array" || typeNode.typeName.name === "ReadonlyArray") return ARRAY_READ_METHOD_NAMES$1.has(methodName);
|
|
27678
|
+
if (typeNode.typeName.name === "Map" || typeNode.typeName.name === "ReadonlyMap") return MAP_READ_METHOD_NAMES.has(methodName);
|
|
27679
|
+
if (typeNode.typeName.name === "Set" || typeNode.typeName.name === "ReadonlySet") return SET_READ_METHOD_NAMES.has(methodName);
|
|
27680
|
+
if (typeNode.typeName.name === "Promise" || typeNode.typeName.name === "PromiseLike") return PROMISE_READ_METHOD_NAMES.has(methodName);
|
|
27681
|
+
return false;
|
|
27682
|
+
};
|
|
27683
|
+
const isProvenNativeReadMethod = (ref, methodName) => Boolean(ref.resolved?.defs.some((definition) => {
|
|
27684
|
+
if (definition.type !== "Parameter") return false;
|
|
27685
|
+
const parameterBinding = getParameterBinding(definition.name);
|
|
27686
|
+
if (!parameterBinding) return false;
|
|
27687
|
+
const typeAnnotation = getTypeAnnotation(parameterBinding);
|
|
27688
|
+
return Boolean(typeAnnotation && isNativeReadMethod(typeAnnotation, methodName, getShadowedNativeTypeNames(parameterBinding)));
|
|
27689
|
+
}));
|
|
27690
|
+
//#endregion
|
|
27358
27691
|
//#region src/plugin/rules/state-and-effects/utils/effect/react.ts
|
|
27359
27692
|
const KNOWN_PURE_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
27360
27693
|
const startsWithUppercase = (name) => Boolean(name && name.length > 0 && name[0] >= "A" && name[0] <= "Z");
|
|
@@ -27581,7 +27914,132 @@ const isRefCurrent = (ref) => {
|
|
|
27581
27914
|
const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isStateSetter(analysis, innerRef));
|
|
27582
27915
|
const isSyncStateSetterCall = (analysis, ref, effectFn) => isStateSetterCall(analysis, ref) && isSynchronous(ref.identifier, effectFn) && !resolvesToAsyncFunction(ref);
|
|
27583
27916
|
const HANDLER_NAMED_METHOD_PATTERN = /^(on|handle)[A-Z]/;
|
|
27584
|
-
const
|
|
27917
|
+
const SYNCHRONOUS_CALLBACK_ARGUMENT_INDEX_BY_METHOD = new Map([
|
|
27918
|
+
["every", 0],
|
|
27919
|
+
["filter", 0],
|
|
27920
|
+
["find", 0],
|
|
27921
|
+
["findIndex", 0],
|
|
27922
|
+
["findLast", 0],
|
|
27923
|
+
["findLastIndex", 0],
|
|
27924
|
+
["flatMap", 0],
|
|
27925
|
+
["forEach", 0],
|
|
27926
|
+
["map", 0],
|
|
27927
|
+
["reduce", 0],
|
|
27928
|
+
["reduceRight", 0],
|
|
27929
|
+
["replace", 1],
|
|
27930
|
+
["replaceAll", 1],
|
|
27931
|
+
["some", 0]
|
|
27932
|
+
]);
|
|
27933
|
+
const addSimpleBindingNames = (pattern, names) => {
|
|
27934
|
+
if (isNodeOfType(pattern, "Identifier")) {
|
|
27935
|
+
if (names.has(pattern.name)) return false;
|
|
27936
|
+
names.add(pattern.name);
|
|
27937
|
+
return true;
|
|
27938
|
+
}
|
|
27939
|
+
if (isNodeOfType(pattern, "AssignmentPattern")) return addSimpleBindingNames(pattern.left, names);
|
|
27940
|
+
if (isNodeOfType(pattern, "RestElement")) return addSimpleBindingNames(pattern.argument, names);
|
|
27941
|
+
if (isNodeOfType(pattern, "ObjectPattern")) {
|
|
27942
|
+
let didAddName = false;
|
|
27943
|
+
for (const property of pattern.properties) didAddName = addSimpleBindingNames(isNodeOfType(property, "Property") ? property.value : property, names) || didAddName;
|
|
27944
|
+
return didAddName;
|
|
27945
|
+
}
|
|
27946
|
+
if (isNodeOfType(pattern, "ArrayPattern")) {
|
|
27947
|
+
let didAddName = false;
|
|
27948
|
+
for (const element of pattern.elements) if (element) didAddName = addSimpleBindingNames(element, names) || didAddName;
|
|
27949
|
+
return didAddName;
|
|
27950
|
+
}
|
|
27951
|
+
return false;
|
|
27952
|
+
};
|
|
27953
|
+
const getSimpleParameterNames = (callback) => {
|
|
27954
|
+
if (!isFunctionLike$1(callback)) return /* @__PURE__ */ new Set();
|
|
27955
|
+
const names = /* @__PURE__ */ new Set();
|
|
27956
|
+
for (const parameter of callback.params ?? []) addSimpleBindingNames(parameter, names);
|
|
27957
|
+
return names;
|
|
27958
|
+
};
|
|
27959
|
+
const inlineCallbackInvokesParameter = (callback) => {
|
|
27960
|
+
const parameterNames = getSimpleParameterNames(callback);
|
|
27961
|
+
if (parameterNames.size === 0 || !isFunctionLike$1(callback)) return false;
|
|
27962
|
+
let didAddAlias = true;
|
|
27963
|
+
while (didAddAlias) {
|
|
27964
|
+
didAddAlias = false;
|
|
27965
|
+
walkAst(callback.body, (candidate) => {
|
|
27966
|
+
if (candidate !== callback.body && isFunctionLike$1(candidate)) return false;
|
|
27967
|
+
if (isNodeOfType(candidate, "AssignmentExpression") && candidate.operator === "=" && isNodeOfType(candidate.left, "Identifier")) {
|
|
27968
|
+
const assignedValueRoot = getRootIdentifier$1(candidate.right);
|
|
27969
|
+
if (isNodeOfType(assignedValueRoot, "Identifier") && parameterNames.has(assignedValueRoot.name) && !parameterNames.has(candidate.left.name)) {
|
|
27970
|
+
parameterNames.add(candidate.left.name);
|
|
27971
|
+
didAddAlias = true;
|
|
27972
|
+
}
|
|
27973
|
+
return;
|
|
27974
|
+
}
|
|
27975
|
+
if (!isNodeOfType(candidate, "VariableDeclarator") || !candidate.init) return;
|
|
27976
|
+
const initializerRoot = getRootIdentifier$1(candidate.init);
|
|
27977
|
+
if (!isNodeOfType(initializerRoot, "Identifier") || !parameterNames.has(initializerRoot.name)) return;
|
|
27978
|
+
if (addSimpleBindingNames(candidate.id, parameterNames)) didAddAlias = true;
|
|
27979
|
+
});
|
|
27980
|
+
}
|
|
27981
|
+
let didInvokeParameter = false;
|
|
27982
|
+
walkAst(callback.body, (candidate) => {
|
|
27983
|
+
if (didInvokeParameter) return false;
|
|
27984
|
+
if (candidate !== callback.body && isFunctionLike$1(candidate)) return false;
|
|
27985
|
+
if (!isNodeOfType(candidate, "CallExpression")) return;
|
|
27986
|
+
const callee = stripParenExpression(candidate.callee);
|
|
27987
|
+
if (isNodeOfType(callee, "Identifier") && parameterNames.has(callee.name)) {
|
|
27988
|
+
didInvokeParameter = true;
|
|
27989
|
+
return false;
|
|
27990
|
+
}
|
|
27991
|
+
if (isNodeOfType(callee, "MemberExpression")) {
|
|
27992
|
+
const receiverRoot = getRootIdentifier$1(callee.object);
|
|
27993
|
+
if (receiverRoot && parameterNames.has(receiverRoot.name)) {
|
|
27994
|
+
didInvokeParameter = true;
|
|
27995
|
+
return false;
|
|
27996
|
+
}
|
|
27997
|
+
}
|
|
27998
|
+
});
|
|
27999
|
+
return didInvokeParameter;
|
|
28000
|
+
};
|
|
28001
|
+
const callbackInvokesPropCallback = (analysis, callback) => {
|
|
28002
|
+
if (!isFunctionLike$1(callback)) return false;
|
|
28003
|
+
let didInvokePropCallback = false;
|
|
28004
|
+
walkAst(callback.body, (candidate) => {
|
|
28005
|
+
if (didInvokePropCallback) return false;
|
|
28006
|
+
if (candidate !== callback.body && isFunctionLike$1(candidate)) return false;
|
|
28007
|
+
if (!isNodeOfType(candidate, "CallExpression")) return;
|
|
28008
|
+
if (getDownstreamRefs(analysis, stripParenExpression(candidate.callee)).some((reference) => isPropCallbackInvocationRef(analysis, reference))) {
|
|
28009
|
+
didInvokePropCallback = true;
|
|
28010
|
+
return false;
|
|
28011
|
+
}
|
|
28012
|
+
});
|
|
28013
|
+
return didInvokePropCallback;
|
|
28014
|
+
};
|
|
28015
|
+
const isNativeMethodSuppressionSafe = (analysis, callExpression, methodName, scopes) => {
|
|
28016
|
+
if (!isNodeOfType(callExpression, "CallExpression")) return false;
|
|
28017
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
28018
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
28019
|
+
const receiver = stripParenExpression(callee.object);
|
|
28020
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
28021
|
+
const receiverSymbol = scopes.symbolFor(receiver);
|
|
28022
|
+
if (!receiverSymbol || hasSymbolWriteBefore(receiverSymbol, callExpression, scopes) || hasPossibleStaticPropertyWriteBefore(receiver, methodName, callExpression, scopes)) return false;
|
|
28023
|
+
let callResult = callExpression;
|
|
28024
|
+
let callResultConsumer = callResult.parent;
|
|
28025
|
+
while (callResultConsumer && (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(callResultConsumer.type) && "expression" in callResultConsumer && callResultConsumer.expression === callResult || isNodeOfType(callResultConsumer, "MemberExpression") && callResultConsumer.object === callResult)) {
|
|
28026
|
+
callResult = callResultConsumer;
|
|
28027
|
+
callResultConsumer = callResult.parent;
|
|
28028
|
+
}
|
|
28029
|
+
if (isNodeOfType(callResultConsumer, "CallExpression") && callResultConsumer.callee === callResult) return false;
|
|
28030
|
+
const callbackArgumentIndex = SYNCHRONOUS_CALLBACK_ARGUMENT_INDEX_BY_METHOD.get(methodName);
|
|
28031
|
+
if (callbackArgumentIndex === void 0) return true;
|
|
28032
|
+
const callbackArgument = callExpression.arguments?.[callbackArgumentIndex];
|
|
28033
|
+
if (!callbackArgument) return true;
|
|
28034
|
+
const callbackValue = stripParenExpression(callbackArgument);
|
|
28035
|
+
if (isFunctionLike$1(callbackValue)) return !inlineCallbackInvokesParameter(callbackValue) && !callbackInvokesPropCallback(analysis, callbackValue);
|
|
28036
|
+
if (!isNodeOfType(callbackValue, "Identifier")) return false;
|
|
28037
|
+
const callbackRef = getRef(analysis, callbackValue);
|
|
28038
|
+
if (!callbackRef || isPropAlias(analysis, callbackRef)) return false;
|
|
28039
|
+
const callbackFunction = resolveToFunction(callbackRef);
|
|
28040
|
+
return Boolean(callbackFunction && isFunctionLike$1(callbackFunction) && !inlineCallbackInvokesParameter(callbackFunction) && !callbackInvokesPropCallback(analysis, callbackFunction));
|
|
28041
|
+
};
|
|
28042
|
+
const isPropCallbackInvocationRef = (analysis, ref, options = {}) => {
|
|
27585
28043
|
if (!isPropAlias(analysis, ref)) return false;
|
|
27586
28044
|
let effectiveNode = ref.identifier;
|
|
27587
28045
|
let parent = effectiveNode.parent;
|
|
@@ -27594,7 +28052,9 @@ const isPropCallbackInvocationRef = (analysis, ref) => {
|
|
|
27594
28052
|
if (isNodeOfType(parent, "MemberExpression") && parent.object === effectiveNode) {
|
|
27595
28053
|
const memberParent = parent.parent;
|
|
27596
28054
|
if (isNodeOfType(memberParent, "CallExpression") && memberParent.callee === parent) {
|
|
27597
|
-
|
|
28055
|
+
const propertyName = getStaticPropertyName(parent);
|
|
28056
|
+
if (propertyName && HANDLER_NAMED_METHOD_PATTERN.test(propertyName)) return true;
|
|
28057
|
+
if (options.nativeMethodScopes && isCustomHookParameter(ref) && propertyName && isProvenNativeReadMethod(ref, propertyName) && isNativeMethodSuppressionSafe(analysis, memberParent, propertyName, options.nativeMethodScopes)) return false;
|
|
27598
28058
|
return isWholePropsObjectReference(analysis, ref);
|
|
27599
28059
|
}
|
|
27600
28060
|
}
|
|
@@ -41065,7 +41525,7 @@ const noPropCallbackInRender = defineRule({
|
|
|
41065
41525
|
if (!analysis) return;
|
|
41066
41526
|
const callee = stripParenExpression(node.callee);
|
|
41067
41527
|
if (isFunctionLike$1(callee)) return;
|
|
41068
|
-
if (!getDownstreamRefs(analysis, callee).some((reference) => isPropCallbackInvocationRef(analysis, reference))) return;
|
|
41528
|
+
if (!getDownstreamRefs(analysis, callee).some((reference) => isPropCallbackInvocationRef(analysis, reference, { nativeMethodScopes: context.scopes }))) return;
|
|
41069
41529
|
context.report({
|
|
41070
41530
|
node,
|
|
41071
41531
|
message: "This prop callback runs during render. React can replay or discard render work, so the callback can fire more than once or for UI that never commits."
|
|
@@ -59029,6 +59489,78 @@ const inferDestructureSourceKey = (bindingIdentifier) => {
|
|
|
59029
59489
|
return null;
|
|
59030
59490
|
};
|
|
59031
59491
|
const isReactUseHook = (hookName) => hookName === "use";
|
|
59492
|
+
const isReactHookCapabilityValue = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
59493
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
59494
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
59495
|
+
const symbol = scopes.symbolFor(unwrappedExpression);
|
|
59496
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
59497
|
+
visitedSymbolIds.add(symbol.id);
|
|
59498
|
+
if (symbol.kind === "import") return isReactImport(symbol) && isReactHookName(getImportedName(symbol.declarationNode) ?? "");
|
|
59499
|
+
if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
|
|
59500
|
+
const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
|
|
59501
|
+
if (destructuredPropertyName) {
|
|
59502
|
+
const namespaceExpression = stripParenExpression(symbol.initializer);
|
|
59503
|
+
return isReactHookName(destructuredPropertyName) && isNodeOfType(namespaceExpression, "Identifier") && isReactNamespaceImport(namespaceExpression, scopes) && !hasPossibleStaticPropertyMutationOrEscape(namespaceExpression, destructuredPropertyName, scopes);
|
|
59504
|
+
}
|
|
59505
|
+
if (symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
|
|
59506
|
+
return isReactHookCapabilityValue(symbol.initializer, scopes, visitedSymbolIds);
|
|
59507
|
+
}
|
|
59508
|
+
if (!isNodeOfType(unwrappedExpression, "MemberExpression")) return false;
|
|
59509
|
+
const propertyName = getStaticPropertyName(unwrappedExpression);
|
|
59510
|
+
if (!propertyName || !isReactHookName(propertyName)) return false;
|
|
59511
|
+
const namespaceExpression = stripParenExpression(unwrappedExpression.object);
|
|
59512
|
+
return isNodeOfType(namespaceExpression, "Identifier") && isReactNamespaceImport(namespaceExpression, scopes) && !hasPossibleStaticPropertyMutationOrEscape(namespaceExpression, propertyName, scopes);
|
|
59513
|
+
};
|
|
59514
|
+
const isReactHookCapabilityComparisonOperand = (expression, scopes) => {
|
|
59515
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
59516
|
+
if (isReactHookCapabilityValue(unwrappedExpression, scopes)) return true;
|
|
59517
|
+
return isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "typeof" && isReactHookCapabilityValue(unwrappedExpression.argument, scopes);
|
|
59518
|
+
};
|
|
59519
|
+
const isStaticCapabilityComparisonValue = (expression, scopes) => {
|
|
59520
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
59521
|
+
if (isNodeOfType(unwrappedExpression, "Literal")) return true;
|
|
59522
|
+
return isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "undefined" && scopes.isGlobalReference(unwrappedExpression);
|
|
59523
|
+
};
|
|
59524
|
+
const CAPABILITY_COMPARISON_OPERATORS = new Set([
|
|
59525
|
+
"==",
|
|
59526
|
+
"!=",
|
|
59527
|
+
"===",
|
|
59528
|
+
"!=="
|
|
59529
|
+
]);
|
|
59530
|
+
const isInvariantReactHookCapabilityCondition = (expression, scopes) => {
|
|
59531
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
59532
|
+
if (isReactHookCapabilityValue(unwrappedExpression, scopes)) return true;
|
|
59533
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return isInvariantReactHookCapabilityCondition(unwrappedExpression.argument, scopes);
|
|
59534
|
+
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) return isInvariantReactHookCapabilityCondition(unwrappedExpression.left, scopes) && isInvariantReactHookCapabilityCondition(unwrappedExpression.right, scopes);
|
|
59535
|
+
if (!isNodeOfType(unwrappedExpression, "BinaryExpression") || !CAPABILITY_COMPARISON_OPERATORS.has(unwrappedExpression.operator)) return false;
|
|
59536
|
+
return isReactHookCapabilityComparisonOperand(unwrappedExpression.left, scopes) && isStaticCapabilityComparisonValue(unwrappedExpression.right, scopes) || isReactHookCapabilityComparisonOperand(unwrappedExpression.right, scopes) && isStaticCapabilityComparisonValue(unwrappedExpression.left, scopes);
|
|
59537
|
+
};
|
|
59538
|
+
const statementContainsOwnAbruptCompletion = (statement) => {
|
|
59539
|
+
let doesContainAbruptCompletion = false;
|
|
59540
|
+
walkAst(statement, (child) => {
|
|
59541
|
+
if (child !== statement && isFunctionLike$1(child)) return false;
|
|
59542
|
+
if (isNodeOfType(child, "ReturnStatement") || isNodeOfType(child, "ThrowStatement")) {
|
|
59543
|
+
doesContainAbruptCompletion = true;
|
|
59544
|
+
return false;
|
|
59545
|
+
}
|
|
59546
|
+
});
|
|
59547
|
+
return doesContainAbruptCompletion;
|
|
59548
|
+
};
|
|
59549
|
+
const isInvariantReactHookCapabilityExit = (statement, scopes) => isNodeOfType(statement, "IfStatement") && statement.alternate === null && statementAlwaysExits(statement.consequent) && isInvariantReactHookCapabilityCondition(statement.test, scopes);
|
|
59550
|
+
const isAfterOnlyInvariantReactHookCapabilityExits = (node, enclosingFunction, scopes) => {
|
|
59551
|
+
if (!isFunctionLike$1(enclosingFunction) || !isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
|
|
59552
|
+
let containingStatement = node;
|
|
59553
|
+
while (containingStatement.parent && containingStatement.parent !== enclosingFunction.body) {
|
|
59554
|
+
if (isFunctionLike$1(containingStatement.parent)) return false;
|
|
59555
|
+
if (isNodeOfType(containingStatement.parent, "IfStatement") || isNodeOfType(containingStatement.parent, "SwitchStatement") || isNodeOfType(containingStatement.parent, "SwitchCase") || isNodeOfType(containingStatement.parent, "ConditionalExpression") || isNodeOfType(containingStatement.parent, "LogicalExpression")) return false;
|
|
59556
|
+
containingStatement = containingStatement.parent;
|
|
59557
|
+
}
|
|
59558
|
+
if (containingStatement.parent !== enclosingFunction.body) return false;
|
|
59559
|
+
const statementIndex = enclosingFunction.body.body.findIndex((statement) => statement === containingStatement);
|
|
59560
|
+
if (statementIndex <= 0) return false;
|
|
59561
|
+
const bypassingStatements = enclosingFunction.body.body.slice(0, statementIndex).filter((statement) => statementContainsOwnAbruptCompletion(statement));
|
|
59562
|
+
return bypassingStatements.length > 0 && bypassingStatements.every((statement) => isInvariantReactHookCapabilityExit(statement, scopes));
|
|
59563
|
+
};
|
|
59032
59564
|
const getCallExpressionCalleeName = (callExpression) => {
|
|
59033
59565
|
const callee = callExpression.callee;
|
|
59034
59566
|
if (isNodeOfType(callee, "Identifier")) return callee.name;
|
|
@@ -59324,7 +59856,7 @@ const rulesOfHooks = defineRule({
|
|
|
59324
59856
|
});
|
|
59325
59857
|
return;
|
|
59326
59858
|
}
|
|
59327
|
-
if (!context.cfg.isUnconditionalFromEntry(node)) context.report({
|
|
59859
|
+
if (!context.cfg.isUnconditionalFromEntry(node) && !isAfterOnlyInvariantReactHookCapabilityExits(node, enclosing.node, context.scopes)) context.report({
|
|
59328
59860
|
node: node.callee,
|
|
59329
59861
|
message: buildConditionalMessage(hookName)
|
|
59330
59862
|
});
|