oxlint-plugin-react-doctor 0.7.7-dev.e5b0690 → 0.7.7-dev.ec144d9
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 +413 -79
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5991,15 +5991,17 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
5991
5991
|
//#endregion
|
|
5992
5992
|
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
5993
5993
|
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
5994
|
-
if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition")) return null;
|
|
5994
|
+
if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition") && !isNodeOfType(node, "MemberExpression")) return null;
|
|
5995
|
+
const key = isNodeOfType(node, "MemberExpression") ? node.property : node.key;
|
|
5995
5996
|
if (node.computed) {
|
|
5996
|
-
if (options.allowComputedString && isNodeOfType(
|
|
5997
|
+
if (options.allowComputedString && isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value;
|
|
5998
|
+
if (options.allowComputedString && isNodeOfType(key, "TemplateLiteral") && key.expressions.length === 0) return key.quasis[0]?.value.cooked ?? key.quasis[0]?.value.raw ?? null;
|
|
5997
5999
|
return null;
|
|
5998
6000
|
}
|
|
5999
|
-
if (isNodeOfType(
|
|
6000
|
-
if (isNodeOfType(
|
|
6001
|
-
if (typeof
|
|
6002
|
-
if (options.stringifyNonStringLiterals) return String(
|
|
6001
|
+
if (isNodeOfType(key, "Identifier")) return key.name;
|
|
6002
|
+
if (isNodeOfType(key, "Literal")) {
|
|
6003
|
+
if (typeof key.value === "string") return key.value;
|
|
6004
|
+
if (options.stringifyNonStringLiterals) return String(key.value);
|
|
6003
6005
|
}
|
|
6004
6006
|
return null;
|
|
6005
6007
|
};
|
|
@@ -11003,6 +11005,14 @@ const PROMISE_CHAIN_METHOD_NAMES$1 = new Set([
|
|
|
11003
11005
|
"catch",
|
|
11004
11006
|
"finally"
|
|
11005
11007
|
]);
|
|
11008
|
+
const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
|
|
11009
|
+
const getPromiseChainCallForCallback = (candidate) => {
|
|
11010
|
+
let callbackContainer = candidate.parent;
|
|
11011
|
+
while (callbackContainer && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(callbackContainer.type)) callbackContainer = callbackContainer.parent;
|
|
11012
|
+
if (!isNodeOfType(callbackContainer, "CallExpression")) return null;
|
|
11013
|
+
if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
|
|
11014
|
+
return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
|
|
11015
|
+
};
|
|
11006
11016
|
const collectEffectInvokedFunctions = (effectCallback) => {
|
|
11007
11017
|
const invokedFunctions = new Set([effectCallback]);
|
|
11008
11018
|
const localFunctionBindings = /* @__PURE__ */ new Map();
|
|
@@ -11014,7 +11024,6 @@ const collectEffectInvokedFunctions = (effectCallback) => {
|
|
|
11014
11024
|
invokedFunctions.add(strippedCandidate);
|
|
11015
11025
|
pendingFunctions.push(strippedCandidate);
|
|
11016
11026
|
};
|
|
11017
|
-
const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
|
|
11018
11027
|
while (pendingFunctions.length > 0) {
|
|
11019
11028
|
const currentFunction = pendingFunctions.pop();
|
|
11020
11029
|
if (!currentFunction) break;
|
|
@@ -11222,6 +11231,50 @@ const isCleanupReturningSubscribeLikeCallExpression = (node) => {
|
|
|
11222
11231
|
return true;
|
|
11223
11232
|
};
|
|
11224
11233
|
//#endregion
|
|
11234
|
+
//#region src/plugin/utils/is-node-reachable-within-function.ts
|
|
11235
|
+
const isInsideStaticallyUnreachableBranch = (node) => {
|
|
11236
|
+
let child = node;
|
|
11237
|
+
let parent = node.parent;
|
|
11238
|
+
while (parent) {
|
|
11239
|
+
if (isNodeOfType(parent, "IfStatement") && isNodeOfType(parent.test, "Literal")) {
|
|
11240
|
+
if (parent.test.value === false && parent.consequent === child) return true;
|
|
11241
|
+
if (parent.test.value === true && parent.alternate === child) return true;
|
|
11242
|
+
}
|
|
11243
|
+
if (isNodeOfType(parent, "ConditionalExpression") && isNodeOfType(parent.test, "Literal")) {
|
|
11244
|
+
if (parent.test.value === false && parent.consequent === child) return true;
|
|
11245
|
+
if (parent.test.value === true && parent.alternate === child) return true;
|
|
11246
|
+
}
|
|
11247
|
+
if (isNodeOfType(parent, "LogicalExpression") && parent.right === child) {
|
|
11248
|
+
if (isNodeOfType(parent.left, "Literal") && (parent.operator === "&&" && !parent.left.value || parent.operator === "||" && Boolean(parent.left.value))) return true;
|
|
11249
|
+
}
|
|
11250
|
+
child = parent;
|
|
11251
|
+
parent = parent.parent;
|
|
11252
|
+
}
|
|
11253
|
+
return false;
|
|
11254
|
+
};
|
|
11255
|
+
const isNodeReachableWithinFunction = (node, context) => {
|
|
11256
|
+
if (isInsideStaticallyUnreachableBranch(node)) return false;
|
|
11257
|
+
const owner = context.cfg.enclosingFunction(node);
|
|
11258
|
+
if (!owner) return true;
|
|
11259
|
+
const functionCfg = context.cfg.cfgFor(owner);
|
|
11260
|
+
if (!functionCfg) return true;
|
|
11261
|
+
const targetBlock = functionCfg.blockOf(node);
|
|
11262
|
+
if (!targetBlock) return true;
|
|
11263
|
+
const visitedBlocks = new Set([functionCfg.entry]);
|
|
11264
|
+
const pendingBlocks = [functionCfg.entry];
|
|
11265
|
+
while (pendingBlocks.length > 0) {
|
|
11266
|
+
const currentBlock = pendingBlocks.pop();
|
|
11267
|
+
if (!currentBlock) break;
|
|
11268
|
+
if (currentBlock === targetBlock) return true;
|
|
11269
|
+
for (const edge of currentBlock.successors) {
|
|
11270
|
+
if (visitedBlocks.has(edge.to)) continue;
|
|
11271
|
+
visitedBlocks.add(edge.to);
|
|
11272
|
+
pendingBlocks.push(edge.to);
|
|
11273
|
+
}
|
|
11274
|
+
}
|
|
11275
|
+
return false;
|
|
11276
|
+
};
|
|
11277
|
+
//#endregion
|
|
11225
11278
|
//#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
|
|
11226
11279
|
const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
|
|
11227
11280
|
const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
|
|
@@ -11350,27 +11403,6 @@ const findSubscribeLikeUsages = (callback, context) => {
|
|
|
11350
11403
|
});
|
|
11351
11404
|
return usages.filter((usage) => isNodeReachableWithinFunction(usage.node, context));
|
|
11352
11405
|
};
|
|
11353
|
-
const isNodeReachableWithinFunction = (node, context) => {
|
|
11354
|
-
const owner = context.cfg.enclosingFunction(node);
|
|
11355
|
-
if (!owner) return true;
|
|
11356
|
-
const functionCfg = context.cfg.cfgFor(owner);
|
|
11357
|
-
if (!functionCfg) return true;
|
|
11358
|
-
const targetBlock = functionCfg.blockOf(node);
|
|
11359
|
-
if (!targetBlock) return true;
|
|
11360
|
-
const visitedBlocks = new Set([functionCfg.entry]);
|
|
11361
|
-
const pendingBlocks = [functionCfg.entry];
|
|
11362
|
-
while (pendingBlocks.length > 0) {
|
|
11363
|
-
const currentBlock = pendingBlocks.pop();
|
|
11364
|
-
if (!currentBlock) break;
|
|
11365
|
-
if (currentBlock === targetBlock) return true;
|
|
11366
|
-
for (const edge of currentBlock.successors) {
|
|
11367
|
-
if (visitedBlocks.has(edge.to)) continue;
|
|
11368
|
-
visitedBlocks.add(edge.to);
|
|
11369
|
-
pendingBlocks.push(edge.to);
|
|
11370
|
-
}
|
|
11371
|
-
}
|
|
11372
|
-
return false;
|
|
11373
|
-
};
|
|
11374
11406
|
const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, context) => {
|
|
11375
11407
|
let pathAnchor = usageNode;
|
|
11376
11408
|
let pathOwner = findEnclosingFunction(pathAnchor);
|
|
@@ -11844,6 +11876,94 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
|
11844
11876
|
return didFindUnmountCleanup;
|
|
11845
11877
|
};
|
|
11846
11878
|
const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
|
|
11879
|
+
const collectBlockingBooleanStates = (expression, blockedExpressionValue, context) => {
|
|
11880
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
11881
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, context);
|
|
11882
|
+
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
|
|
11883
|
+
if (!(unwrappedExpression.operator === "||" && blockedExpressionValue || unwrappedExpression.operator === "&&" && !blockedExpressionValue)) return [];
|
|
11884
|
+
return [...collectBlockingBooleanStates(unwrappedExpression.left, blockedExpressionValue, context), ...collectBlockingBooleanStates(unwrappedExpression.right, blockedExpressionValue, context)];
|
|
11885
|
+
}
|
|
11886
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression") && [
|
|
11887
|
+
"===",
|
|
11888
|
+
"==",
|
|
11889
|
+
"!==",
|
|
11890
|
+
"!="
|
|
11891
|
+
].includes(unwrappedExpression.operator)) {
|
|
11892
|
+
const leftValue = readStaticBoolean(unwrappedExpression.left);
|
|
11893
|
+
const rightValue = readStaticBoolean(unwrappedExpression.right);
|
|
11894
|
+
const booleanValue = leftValue ?? rightValue;
|
|
11895
|
+
const comparedKey = resolveExpressionKey(leftValue === null ? unwrappedExpression.left : unwrappedExpression.right, context);
|
|
11896
|
+
if (booleanValue === null || comparedKey === null) return [];
|
|
11897
|
+
return [{
|
|
11898
|
+
key: comparedKey,
|
|
11899
|
+
value: (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "==") === blockedExpressionValue ? booleanValue : !booleanValue
|
|
11900
|
+
}];
|
|
11901
|
+
}
|
|
11902
|
+
const expressionKey = resolveExpressionKey(unwrappedExpression, context);
|
|
11903
|
+
return expressionKey === null ? [] : [{
|
|
11904
|
+
key: expressionKey,
|
|
11905
|
+
value: blockedExpressionValue
|
|
11906
|
+
}];
|
|
11907
|
+
};
|
|
11908
|
+
const isDirectEarlyReturnConsequent = (ifStatement) => {
|
|
11909
|
+
if (!isNodeOfType(ifStatement, "IfStatement") || ifStatement.alternate) return false;
|
|
11910
|
+
if (isNodeOfType(ifStatement.consequent, "ReturnStatement")) return true;
|
|
11911
|
+
return isNodeOfType(ifStatement.consequent, "BlockStatement") && ifStatement.consequent.body.length === 1 && isNodeOfType(ifStatement.consequent.body[0], "ReturnStatement");
|
|
11912
|
+
};
|
|
11913
|
+
const collectDeferredUsageGuardStates = (callback, usageNode, context) => {
|
|
11914
|
+
if (!isFunctionLike$2(callback) || callback.async) return [];
|
|
11915
|
+
const guardStates = [];
|
|
11916
|
+
walkAst(callback.body, (child) => {
|
|
11917
|
+
if (child !== callback.body && isFunctionLike$2(child)) return false;
|
|
11918
|
+
if (isNodeOfType(child, "IfStatement") && isDirectEarlyReturnConsequent(child) && doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], callback, context)) guardStates.push(...collectBlockingBooleanStates(child.test, true, context));
|
|
11919
|
+
});
|
|
11920
|
+
let descendant = usageNode;
|
|
11921
|
+
let ancestor = descendant.parent;
|
|
11922
|
+
while (ancestor && ancestor !== callback) {
|
|
11923
|
+
if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant) guardStates.push(...collectBlockingBooleanStates(ancestor.test, false, context));
|
|
11924
|
+
descendant = ancestor;
|
|
11925
|
+
ancestor = ancestor.parent;
|
|
11926
|
+
}
|
|
11927
|
+
return guardStates;
|
|
11928
|
+
};
|
|
11929
|
+
const cleanupReturnInvalidatesGuard = (cleanupReturn, guardState, context) => {
|
|
11930
|
+
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
|
|
11931
|
+
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
11932
|
+
if (!cleanupFunction || !isFunctionLike$2(cleanupFunction) || cleanupFunction.async) return false;
|
|
11933
|
+
let didInvalidateGuard = false;
|
|
11934
|
+
walkAst(cleanupFunction.body, (child) => {
|
|
11935
|
+
if (didInvalidateGuard) return false;
|
|
11936
|
+
if (child !== cleanupFunction.body && isFunctionLike$2(child)) return false;
|
|
11937
|
+
if (isNodeOfType(child, "AssignmentExpression") && child.operator === "=" && resolveExpressionKey(child.left, context) === guardState.key && readStaticBoolean(child.right) === guardState.value && context.cfg.isUnconditionalFromEntry(child)) {
|
|
11938
|
+
didInvalidateGuard = true;
|
|
11939
|
+
return false;
|
|
11940
|
+
}
|
|
11941
|
+
});
|
|
11942
|
+
return didInvalidateGuard;
|
|
11943
|
+
};
|
|
11944
|
+
const deferredUsageWritesGuardBeforeUsage = (callback, usageNode, guardState, context) => {
|
|
11945
|
+
const usageStart = getRangeStart(usageNode);
|
|
11946
|
+
if (!isFunctionLike$2(callback) || usageStart === null) return true;
|
|
11947
|
+
let didWriteGuard = false;
|
|
11948
|
+
walkAst(callback.body, (child) => {
|
|
11949
|
+
if (didWriteGuard) return false;
|
|
11950
|
+
if (child !== callback.body && isFunctionLike$2(child)) return false;
|
|
11951
|
+
const childStart = getRangeStart(child);
|
|
11952
|
+
if (childStart === null || childStart >= usageStart) return;
|
|
11953
|
+
const writtenExpression = isNodeOfType(child, "AssignmentExpression") ? child.left : isNodeOfType(child, "UpdateExpression") ? child.argument : null;
|
|
11954
|
+
if (writtenExpression && resolveExpressionKey(writtenExpression, context) === guardState.key) {
|
|
11955
|
+
didWriteGuard = true;
|
|
11956
|
+
return false;
|
|
11957
|
+
}
|
|
11958
|
+
});
|
|
11959
|
+
return didWriteGuard;
|
|
11960
|
+
};
|
|
11961
|
+
const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
|
|
11962
|
+
const usageFunction = findEnclosingFunction(usage.node);
|
|
11963
|
+
const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
|
|
11964
|
+
if (usage.handleKey === null || !usageFunction || usageFunction === callback || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
|
|
11965
|
+
return collectDeferredUsageGuardStates(usageFunction, usage.node, context).some((guardState) => !deferredUsageWritesGuardBeforeUsage(usageFunction, usage.node, guardState, context) && cleanupReturns.every((cleanupReturn) => cleanupReturnInvalidatesGuard(cleanupReturn, guardState, context)));
|
|
11966
|
+
};
|
|
11847
11967
|
const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
11848
11968
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
11849
11969
|
if (callback.async) return false;
|
|
@@ -11883,6 +12003,7 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
|
11883
12003
|
if (!cleanupFunction || !isFunctionLike$2(cleanupFunction)) return;
|
|
11884
12004
|
if (doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context)) matchingCleanupReturns.push(child);
|
|
11885
12005
|
});
|
|
12006
|
+
if (hasGuardedDeferredCleanup(callback, usage, matchingCleanupReturns, context)) return true;
|
|
11886
12007
|
return doMatchingNodesCoverEveryPathAfterUsage(resolveCleanupPathAnchor(usage.node, callback, context), matchingCleanupReturns, context);
|
|
11887
12008
|
};
|
|
11888
12009
|
const findFirstUsageWithoutCleanup = (callback, usages, context) => {
|
|
@@ -17201,7 +17322,7 @@ const GLOBAL_BUILTIN_NAMES = new Set([
|
|
|
17201
17322
|
]);
|
|
17202
17323
|
const STRING_PROTOTYPE_PATH = "String.prototype";
|
|
17203
17324
|
const REGEXP_PROTOTYPE_PATH = "RegExp.prototype";
|
|
17204
|
-
const getStaticStringValue = (argument) => {
|
|
17325
|
+
const getStaticStringValue$1 = (argument) => {
|
|
17205
17326
|
if (!argument) return null;
|
|
17206
17327
|
const unwrappedArgument = stripParenExpression(argument);
|
|
17207
17328
|
if (isNodeOfType(unwrappedArgument, "Literal") && typeof unwrappedArgument.value === "string") return unwrappedArgument.value;
|
|
@@ -17209,7 +17330,7 @@ const getStaticStringValue = (argument) => {
|
|
|
17209
17330
|
return null;
|
|
17210
17331
|
};
|
|
17211
17332
|
const getEffectiveRegExpFlags = (patternArgument, flagsArgument) => {
|
|
17212
|
-
if (flagsArgument) return getStaticStringValue(flagsArgument);
|
|
17333
|
+
if (flagsArgument) return getStaticStringValue$1(flagsArgument);
|
|
17213
17334
|
if (!patternArgument) return "";
|
|
17214
17335
|
const unwrappedPattern = stripParenExpression(patternArgument);
|
|
17215
17336
|
if (isNodeOfType(unwrappedPattern, "Literal") && unwrappedPattern.value instanceof RegExp) return unwrappedPattern.value.flags;
|
|
@@ -17359,7 +17480,7 @@ const getCallRegExpHazard = (node, context, symbolCache) => {
|
|
|
17359
17480
|
const mutationHazard = targetPath === "global" ? "globalRegExpReplaced" : "replaceAllIntegrityLost";
|
|
17360
17481
|
const guardedPropertyName = targetPath === "global" ? "RegExp" : "replaceAll";
|
|
17361
17482
|
if (isSinglePropertyMutation) {
|
|
17362
|
-
const propertyName = getStaticStringValue(node.arguments?.[1]);
|
|
17483
|
+
const propertyName = getStaticStringValue$1(node.arguments?.[1]);
|
|
17363
17484
|
return propertyName === null || propertyName === guardedPropertyName ? mutationHazard : "none";
|
|
17364
17485
|
}
|
|
17365
17486
|
if (!isPropertyCollectionMutation) return "none";
|
|
@@ -25449,8 +25570,11 @@ const unwrapUseCallback = (node) => {
|
|
|
25449
25570
|
const callee = node.callee;
|
|
25450
25571
|
return isNodeOfType(callee, "Identifier") && callee.name === "useCallback" || isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useCallback" ? node.arguments?.[0] : node;
|
|
25451
25572
|
};
|
|
25573
|
+
const hasParameterDefinition = (ref) => Boolean(ref.resolved?.defs.some((definition) => definition.type === "Parameter"));
|
|
25452
25574
|
const resolveToFunction = (ref) => {
|
|
25453
|
-
const
|
|
25575
|
+
const definition = ref.resolved?.defs[0];
|
|
25576
|
+
if (!definition || hasParameterDefinition(ref)) return null;
|
|
25577
|
+
const definitionNode = definition.node;
|
|
25454
25578
|
if (!definitionNode) return null;
|
|
25455
25579
|
if (isFunctionLike$2(definitionNode)) return definitionNode;
|
|
25456
25580
|
if (isNodeOfType(definitionNode, "VariableDeclarator")) {
|
|
@@ -25860,7 +25984,7 @@ const isCleanupReturnArgument = (analysis, node) => {
|
|
|
25860
25984
|
if (isNodeOfType(node, "MemberExpression")) return true;
|
|
25861
25985
|
if (isNodeOfType(node, "Identifier")) {
|
|
25862
25986
|
const ref = getRef(analysis, node);
|
|
25863
|
-
if (ref && resolveToFunction(ref)) return true;
|
|
25987
|
+
if (ref && (hasParameterDefinition(ref) || resolveToFunction(ref))) return true;
|
|
25864
25988
|
}
|
|
25865
25989
|
if (isNodeOfType(node, "ConditionalExpression")) return isCleanupReturnArgument(analysis, node.consequent) || isCleanupReturnArgument(analysis, node.alternate);
|
|
25866
25990
|
return false;
|
|
@@ -26332,7 +26456,7 @@ const resolveWrappedCallable = (analysis, node) => {
|
|
|
26332
26456
|
if (isNodeOfType(candidate, "Identifier")) {
|
|
26333
26457
|
const reference = getRef(analysis, candidate);
|
|
26334
26458
|
if (!reference) return null;
|
|
26335
|
-
if (reference.resolved?.defs.some((definition) => definition.type === "
|
|
26459
|
+
if (reference.resolved?.defs.some((definition) => definition.type === "ImportBinding")) return null;
|
|
26336
26460
|
const resolved = resolveToFunction(reference);
|
|
26337
26461
|
if (resolved) return resolved;
|
|
26338
26462
|
const definitionNode = reference.resolved?.defs[0]?.node;
|
|
@@ -38141,8 +38265,6 @@ const getWrapperHookWrappedFunction = (initializer) => {
|
|
|
38141
38265
|
if (!wrapped || !isFunctionLike$2(wrapped)) return null;
|
|
38142
38266
|
return wrapped;
|
|
38143
38267
|
};
|
|
38144
|
-
const hasParameterDef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "Parameter"));
|
|
38145
|
-
const resolvesToFunctionBinding = (ref) => !hasParameterDef(ref) && Boolean(resolveToFunction(ref));
|
|
38146
38268
|
const HANDLER_NAMED_PROP_PATTERN = /^(on|handle)[A-Z]/;
|
|
38147
38269
|
const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstreamRefs(analysis, wrappedFunction).some((innerRef) => {
|
|
38148
38270
|
if (!isProp(analysis, innerRef)) return false;
|
|
@@ -38463,7 +38585,7 @@ const noPassDataToParent = defineRule({
|
|
|
38463
38585
|
if (isConstant(argRef)) return false;
|
|
38464
38586
|
if (isParentWiredHookResultRef(analysis, argRef)) return false;
|
|
38465
38587
|
if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
|
|
38466
|
-
if (
|
|
38588
|
+
if (resolveToFunction(argRef)) return false;
|
|
38467
38589
|
const argIdentifier = argRef.identifier;
|
|
38468
38590
|
if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
|
|
38469
38591
|
if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
|
|
@@ -46894,7 +47016,258 @@ const queryMutationMissingInvalidation = defineRule({
|
|
|
46894
47016
|
}
|
|
46895
47017
|
});
|
|
46896
47018
|
//#endregion
|
|
47019
|
+
//#region src/plugin/utils/is-node-conditionally-executed.ts
|
|
47020
|
+
const isNodeConditionallyExecuted = (node, boundary) => {
|
|
47021
|
+
let child = node;
|
|
47022
|
+
let parent = child.parent ?? null;
|
|
47023
|
+
while (parent && parent !== boundary) {
|
|
47024
|
+
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === child || parent.alternate === child)) return true;
|
|
47025
|
+
if (isNodeOfType(parent, "LogicalExpression") && parent.right === child) return true;
|
|
47026
|
+
if (isNodeOfType(parent, "AssignmentPattern") && parent.right === child) return true;
|
|
47027
|
+
child = parent;
|
|
47028
|
+
parent = child.parent ?? null;
|
|
47029
|
+
}
|
|
47030
|
+
return false;
|
|
47031
|
+
};
|
|
47032
|
+
//#endregion
|
|
47033
|
+
//#region src/plugin/rules/tanstack-query/utils/resolve-tanstack-query-hook-name.ts
|
|
47034
|
+
const resolveTanstackNamespaceHookName = (memberExpression, contextNode, scopes) => {
|
|
47035
|
+
const hookName = getStaticPropertyKeyName(memberExpression, { allowComputedString: true });
|
|
47036
|
+
const namespaceObject = stripParenExpression(memberExpression.object);
|
|
47037
|
+
if (!hookName || !TANSTACK_QUERY_HOOKS.has(hookName)) return null;
|
|
47038
|
+
if (!isNodeOfType(namespaceObject, "Identifier")) return null;
|
|
47039
|
+
const resolvedNamespaceSymbol = resolveConstIdentifierAlias(namespaceObject, scopes);
|
|
47040
|
+
if (resolvedNamespaceSymbol?.kind !== "import") return null;
|
|
47041
|
+
const namespaceBinding = getImportBindingForName(contextNode, resolvedNamespaceSymbol.name);
|
|
47042
|
+
return namespaceBinding?.isNamespace && isTanstackQuerySource(namespaceBinding.source) ? hookName : null;
|
|
47043
|
+
};
|
|
47044
|
+
const resolveTanstackQueryHookName = (callExpression, scopes) => {
|
|
47045
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
47046
|
+
if (isNodeOfType(callee, "Identifier")) {
|
|
47047
|
+
const resolvedSymbol = resolveConstIdentifierAlias(callee, scopes);
|
|
47048
|
+
if (!resolvedSymbol) return null;
|
|
47049
|
+
if (resolvedSymbol.kind === "const" && resolvedSymbol.initializer) {
|
|
47050
|
+
const initializer = stripParenExpression(resolvedSymbol.initializer);
|
|
47051
|
+
return isNodeOfType(initializer, "MemberExpression") ? resolveTanstackNamespaceHookName(initializer, callExpression, scopes) : null;
|
|
47052
|
+
}
|
|
47053
|
+
if (resolvedSymbol.kind !== "import") return null;
|
|
47054
|
+
const importBinding = getImportBindingForName(callExpression, resolvedSymbol.name);
|
|
47055
|
+
if (importBinding === null) return null;
|
|
47056
|
+
if (importBinding.isNamespace || !isTanstackQuerySource(importBinding.source)) return null;
|
|
47057
|
+
return importBinding.exportedName !== null && TANSTACK_QUERY_HOOKS.has(importBinding.exportedName) ? importBinding.exportedName : null;
|
|
47058
|
+
}
|
|
47059
|
+
if (isNodeOfType(callee, "MemberExpression")) return resolveTanstackNamespaceHookName(callee, callExpression, scopes);
|
|
47060
|
+
return null;
|
|
47061
|
+
};
|
|
47062
|
+
const resolveTanstackQueryHookNameFromInitializer = (initializer, scopes) => {
|
|
47063
|
+
const unwrappedInitializer = stripParenExpression(initializer);
|
|
47064
|
+
if (isNodeOfType(unwrappedInitializer, "CallExpression")) return resolveTanstackQueryHookName(unwrappedInitializer, scopes);
|
|
47065
|
+
if (!isNodeOfType(unwrappedInitializer, "Identifier")) return null;
|
|
47066
|
+
const resolvedSymbol = resolveConstIdentifierAlias(unwrappedInitializer, scopes);
|
|
47067
|
+
if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
|
|
47068
|
+
const resolvedInitializer = stripParenExpression(resolvedSymbol.initializer);
|
|
47069
|
+
if (!isNodeOfType(resolvedInitializer, "CallExpression")) return null;
|
|
47070
|
+
return resolveTanstackQueryHookName(resolvedInitializer, scopes);
|
|
47071
|
+
};
|
|
47072
|
+
//#endregion
|
|
46897
47073
|
//#region src/plugin/rules/tanstack-query/query-no-query-in-effect.ts
|
|
47074
|
+
const isTanstackQueryResult = (expression, context) => Boolean(resolveTanstackQueryHookNameFromInitializer(expression, context.scopes));
|
|
47075
|
+
const isStaticRefetchMember = (memberExpression) => getStaticPropertyKeyName(memberExpression, { allowComputedString: true }) === "refetch";
|
|
47076
|
+
const resolveCalledFunction = (callee, context) => {
|
|
47077
|
+
const unwrappedCallee = stripParenExpression(callee);
|
|
47078
|
+
if (isFunctionLike$2(unwrappedCallee)) return unwrappedCallee;
|
|
47079
|
+
if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
|
|
47080
|
+
const symbol = resolveConstIdentifierAlias(unwrappedCallee, context.scopes);
|
|
47081
|
+
if (!symbol) return null;
|
|
47082
|
+
const candidate = symbol.kind === "function" ? symbol.declarationNode : symbol.initializer;
|
|
47083
|
+
return candidate && isFunctionLike$2(candidate) ? candidate : null;
|
|
47084
|
+
};
|
|
47085
|
+
const hasSuspensionBefore = (functionNode, boundary, context) => {
|
|
47086
|
+
if (!isFunctionLike$2(functionNode)) return true;
|
|
47087
|
+
if (functionNode.generator) return true;
|
|
47088
|
+
const boundaryStart = getRangeStart(boundary);
|
|
47089
|
+
if (boundaryStart === null) return true;
|
|
47090
|
+
let hasSuspension = false;
|
|
47091
|
+
walkAst(functionNode, (node) => {
|
|
47092
|
+
if (node !== functionNode && isFunctionLike$2(node)) return false;
|
|
47093
|
+
if (!isNodeOfType(node, "AwaitExpression") || !isNodeReachableWithinFunction(node, context)) return;
|
|
47094
|
+
const suspensionStart = getRangeStart(node);
|
|
47095
|
+
if (suspensionStart !== null && suspensionStart < boundaryStart) {
|
|
47096
|
+
hasSuspension = true;
|
|
47097
|
+
return false;
|
|
47098
|
+
}
|
|
47099
|
+
});
|
|
47100
|
+
return hasSuspension;
|
|
47101
|
+
};
|
|
47102
|
+
const isFunctionAncestor = (ancestor, functionNode) => {
|
|
47103
|
+
let enclosingFunction = findEnclosingFunction(functionNode);
|
|
47104
|
+
while (enclosingFunction) {
|
|
47105
|
+
if (enclosingFunction === ancestor) return true;
|
|
47106
|
+
enclosingFunction = findEnclosingFunction(enclosingFunction);
|
|
47107
|
+
}
|
|
47108
|
+
return false;
|
|
47109
|
+
};
|
|
47110
|
+
const isUnconditionallyExecuted = (node, functionNode, context) => context.cfg.isUnconditionalFromEntry(node) && !isNodeConditionallyExecuted(node, functionNode) && !(isNodeOfType(node, "MemberExpression") && isNodeOfType(node.parent, "AssignmentExpression") && node.parent.left === node && (node.parent.operator === "&&=" || node.parent.operator === "||=" || node.parent.operator === "??="));
|
|
47111
|
+
const functionInvokesTarget = (callerFunction, targetFunction, context, visitedFunctions, canCrossSuspension = false) => {
|
|
47112
|
+
if (visitedFunctions.has(callerFunction)) return false;
|
|
47113
|
+
visitedFunctions.add(callerFunction);
|
|
47114
|
+
let invokesTarget = false;
|
|
47115
|
+
walkAst(callerFunction, (node) => {
|
|
47116
|
+
if (node !== callerFunction && isFunctionLike$2(node)) return false;
|
|
47117
|
+
if (!isNodeOfType(node, "CallExpression")) return;
|
|
47118
|
+
if (!isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, callerFunction, context) || !canCrossSuspension && hasSuspensionBefore(callerFunction, node, context)) return;
|
|
47119
|
+
const calledFunction = resolveCalledFunction(node.callee, context);
|
|
47120
|
+
if (calledFunction === targetFunction || calledFunction && functionInvokesTarget(calledFunction, targetFunction, context, visitedFunctions, canCrossSuspension)) {
|
|
47121
|
+
invokesTarget = true;
|
|
47122
|
+
return false;
|
|
47123
|
+
}
|
|
47124
|
+
});
|
|
47125
|
+
return invokesTarget;
|
|
47126
|
+
};
|
|
47127
|
+
const isFunctionInvokedBefore = (invokedFunction, boundary, context) => {
|
|
47128
|
+
const boundaryFunction = findEnclosingFunction(boundary);
|
|
47129
|
+
const boundaryStart = getRangeStart(boundary);
|
|
47130
|
+
if (!boundaryFunction || boundaryStart === null) return false;
|
|
47131
|
+
let isInvokedBefore = false;
|
|
47132
|
+
walkAst(boundaryFunction, (node) => {
|
|
47133
|
+
if (node !== boundaryFunction && isFunctionLike$2(node)) return false;
|
|
47134
|
+
if (!isNodeOfType(node, "CallExpression")) return;
|
|
47135
|
+
const callStart = getRangeStart(node);
|
|
47136
|
+
if (callStart === null || callStart >= boundaryStart || !isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, boundaryFunction, context) || hasSuspensionBefore(boundaryFunction, node, context)) return;
|
|
47137
|
+
const calledFunction = resolveCalledFunction(node.callee, context);
|
|
47138
|
+
if (calledFunction === invokedFunction || calledFunction && functionInvokesTarget(calledFunction, invokedFunction, context, /* @__PURE__ */ new Set())) {
|
|
47139
|
+
isInvokedBefore = true;
|
|
47140
|
+
return false;
|
|
47141
|
+
}
|
|
47142
|
+
});
|
|
47143
|
+
return isInvokedBefore;
|
|
47144
|
+
};
|
|
47145
|
+
const isFunctionInvokedAfter = (invokedFunction, boundary, callerFunction, context) => {
|
|
47146
|
+
const boundaryStart = getRangeStart(boundary);
|
|
47147
|
+
if (boundaryStart === null) return false;
|
|
47148
|
+
let isInvokedAfter = false;
|
|
47149
|
+
walkAst(callerFunction, (node) => {
|
|
47150
|
+
if (node !== callerFunction && isFunctionLike$2(node)) return false;
|
|
47151
|
+
if (!isNodeOfType(node, "CallExpression")) return;
|
|
47152
|
+
const callStart = getRangeStart(node);
|
|
47153
|
+
if (callStart === null || callStart <= boundaryStart || !isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, callerFunction, context)) return;
|
|
47154
|
+
const calledFunction = resolveCalledFunction(node.callee, context);
|
|
47155
|
+
if (calledFunction === invokedFunction || calledFunction && functionInvokesTarget(calledFunction, invokedFunction, context, /* @__PURE__ */ new Set(), true)) {
|
|
47156
|
+
isInvokedAfter = true;
|
|
47157
|
+
return false;
|
|
47158
|
+
}
|
|
47159
|
+
});
|
|
47160
|
+
return isInvokedAfter;
|
|
47161
|
+
};
|
|
47162
|
+
const isWriteExecutedBefore = (writeNode, boundary, context, deferredExecutionFunction) => {
|
|
47163
|
+
const writeStart = getRangeStart(writeNode);
|
|
47164
|
+
const boundaryStart = getRangeStart(boundary);
|
|
47165
|
+
const writeFunction = findEnclosingFunction(writeNode);
|
|
47166
|
+
const writeExecutionBoundary = writeFunction ?? findProgramRoot(writeNode);
|
|
47167
|
+
if (writeStart === null || boundaryStart === null || !writeExecutionBoundary || !isNodeReachableWithinFunction(writeNode, context) || !isUnconditionallyExecuted(writeNode, writeExecutionBoundary, context)) return false;
|
|
47168
|
+
const boundaryFunction = findEnclosingFunction(boundary);
|
|
47169
|
+
const renderFunction = deferredExecutionFunction ? findEnclosingFunction(deferredExecutionFunction) : null;
|
|
47170
|
+
if (writeFunction === boundaryFunction) return writeStart < boundaryStart;
|
|
47171
|
+
if (!writeFunction) return writeStart < boundaryStart;
|
|
47172
|
+
if (boundaryFunction && isFunctionAncestor(writeFunction, boundaryFunction)) {
|
|
47173
|
+
if (Boolean(renderFunction && (writeFunction === renderFunction || isFunctionAncestor(writeFunction, renderFunction)))) return true;
|
|
47174
|
+
if (deferredExecutionFunction && boundaryFunction !== deferredExecutionFunction) {
|
|
47175
|
+
if (hasSuspensionBefore(boundaryFunction, boundary, context) && isFunctionInvokedBefore(boundaryFunction, writeNode, context)) return true;
|
|
47176
|
+
return isFunctionInvokedAfter(boundaryFunction, writeNode, writeFunction, context);
|
|
47177
|
+
}
|
|
47178
|
+
return writeStart < boundaryStart;
|
|
47179
|
+
}
|
|
47180
|
+
if (renderFunction && isFunctionAncestor(renderFunction, writeFunction) && !hasSuspensionBefore(writeFunction, writeNode, context) && functionInvokesTarget(renderFunction, writeFunction, context, /* @__PURE__ */ new Set())) return true;
|
|
47181
|
+
return !hasSuspensionBefore(writeFunction, writeNode, context) && isFunctionInvokedBefore(writeFunction, boundary, context);
|
|
47182
|
+
};
|
|
47183
|
+
const getStaticStringValue = (node) => {
|
|
47184
|
+
const unwrappedNode = stripParenExpression(node);
|
|
47185
|
+
if (isNodeOfType(unwrappedNode, "Literal") && typeof unwrappedNode.value === "string") return unwrappedNode.value;
|
|
47186
|
+
if (isNodeOfType(unwrappedNode, "TemplateLiteral") && unwrappedNode.expressions.length === 0) return getStaticTemplateLiteralValue(unwrappedNode);
|
|
47187
|
+
return null;
|
|
47188
|
+
};
|
|
47189
|
+
const isSameRefetchMember = (target, candidate, context) => {
|
|
47190
|
+
const unwrappedTarget = stripParenExpression(target);
|
|
47191
|
+
const unwrappedCandidate = stripParenExpression(candidate);
|
|
47192
|
+
if (!isNodeOfType(unwrappedTarget, "Identifier") || !isNodeOfType(unwrappedCandidate, "MemberExpression") || !isStaticRefetchMember(unwrappedCandidate)) return false;
|
|
47193
|
+
const candidateTarget = stripParenExpression(unwrappedCandidate.object);
|
|
47194
|
+
if (!isNodeOfType(candidateTarget, "Identifier")) return false;
|
|
47195
|
+
const targetSymbol = resolveConstIdentifierAlias(unwrappedTarget, context.scopes);
|
|
47196
|
+
const candidateSymbol = resolveConstIdentifierAlias(candidateTarget, context.scopes);
|
|
47197
|
+
return Boolean(targetSymbol && candidateSymbol?.id === targetSymbol.id);
|
|
47198
|
+
};
|
|
47199
|
+
const getRefetchMutationTarget = (node, context) => {
|
|
47200
|
+
if (isNodeOfType(node, "MemberExpression") && isStaticRefetchMember(node)) {
|
|
47201
|
+
const parent = node.parent;
|
|
47202
|
+
if (isNodeOfType(parent, "AssignmentExpression") && parent.left === node && isSameRefetchMember(node.object, parent.right, context)) return null;
|
|
47203
|
+
return isNodeOfType(parent, "AssignmentExpression") && parent.left === node || isNodeOfType(parent, "UpdateExpression") && parent.argument === node || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" ? node.object : null;
|
|
47204
|
+
}
|
|
47205
|
+
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
47206
|
+
const callee = stripParenExpression(node.callee);
|
|
47207
|
+
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
47208
|
+
const receiver = stripParenExpression(callee.object);
|
|
47209
|
+
if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || context.scopes.symbolFor(receiver)) return null;
|
|
47210
|
+
const methodName = getStaticPropertyKeyName(callee, { allowComputedString: true });
|
|
47211
|
+
const target = node.arguments[0];
|
|
47212
|
+
if (!target || isNodeOfType(target, "SpreadElement")) return null;
|
|
47213
|
+
if (methodName === "defineProperty") {
|
|
47214
|
+
const propertyKey = node.arguments[1];
|
|
47215
|
+
if (!propertyKey || getStaticStringValue(propertyKey) !== "refetch") return null;
|
|
47216
|
+
const descriptor = node.arguments[2];
|
|
47217
|
+
if (isNodeOfType(descriptor, "ObjectExpression")) {
|
|
47218
|
+
const valueProperty = descriptor.properties.find((property) => isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "value");
|
|
47219
|
+
if (isNodeOfType(valueProperty, "Property") && isSameRefetchMember(target, valueProperty.value, context)) return null;
|
|
47220
|
+
}
|
|
47221
|
+
return target;
|
|
47222
|
+
}
|
|
47223
|
+
if (methodName !== "assign") return null;
|
|
47224
|
+
let finalRefetchValue = null;
|
|
47225
|
+
for (const source of node.arguments.slice(1)) {
|
|
47226
|
+
if (!isNodeOfType(source, "ObjectExpression")) continue;
|
|
47227
|
+
for (const property of source.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "refetch") finalRefetchValue = property.value;
|
|
47228
|
+
}
|
|
47229
|
+
if (!finalRefetchValue || isSameRefetchMember(target, finalRefetchValue, context)) return null;
|
|
47230
|
+
return target;
|
|
47231
|
+
};
|
|
47232
|
+
const hasRefetchMemberWriteBefore = (expression, boundary, context, deferredExecutionFunction) => {
|
|
47233
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
47234
|
+
if (!isNodeOfType(unwrappedExpression, "Identifier")) return false;
|
|
47235
|
+
const resultSymbol = resolveConstIdentifierAlias(unwrappedExpression, context.scopes);
|
|
47236
|
+
if (!resultSymbol) return false;
|
|
47237
|
+
const program = findProgramRoot(expression);
|
|
47238
|
+
if (!program) return true;
|
|
47239
|
+
if (getRangeStart(boundary) === null) return true;
|
|
47240
|
+
let hasWrite = false;
|
|
47241
|
+
walkAst(program, (node) => {
|
|
47242
|
+
const mutationTarget = getRefetchMutationTarget(node, context);
|
|
47243
|
+
if (!mutationTarget) return;
|
|
47244
|
+
if (!isWriteExecutedBefore(node, boundary, context, deferredExecutionFunction)) return;
|
|
47245
|
+
const object = stripParenExpression(mutationTarget);
|
|
47246
|
+
if (!isNodeOfType(object, "Identifier")) return;
|
|
47247
|
+
if (resolveConstIdentifierAlias(object, context.scopes)?.id === resultSymbol.id) {
|
|
47248
|
+
hasWrite = true;
|
|
47249
|
+
return false;
|
|
47250
|
+
}
|
|
47251
|
+
});
|
|
47252
|
+
return hasWrite;
|
|
47253
|
+
};
|
|
47254
|
+
const isTanstackRefetchExpression = (expression, context, deferredExecutionFunction, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
47255
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
47256
|
+
if (isNodeOfType(unwrappedExpression, "MemberExpression")) return isStaticRefetchMember(unwrappedExpression) && isTanstackQueryResult(unwrappedExpression.object, context) && !hasRefetchMemberWriteBefore(unwrappedExpression.object, unwrappedExpression, context, deferredExecutionFunction);
|
|
47257
|
+
if (!isNodeOfType(unwrappedExpression, "Identifier")) return false;
|
|
47258
|
+
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
47259
|
+
if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read") || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
|
|
47260
|
+
visitedSymbolIds.add(symbol.id);
|
|
47261
|
+
const bindingProperty = symbol.bindingIdentifier.parent;
|
|
47262
|
+
if (isNodeOfType(bindingProperty, "Property") && getStaticPropertyKeyName(bindingProperty, { allowComputedString: true }) === "refetch") {
|
|
47263
|
+
const initializer = symbol.declarationNode.init;
|
|
47264
|
+
return Boolean(initializer && isTanstackQueryResult(initializer, context) && !hasRefetchMemberWriteBefore(initializer, symbol.declarationNode, context, null));
|
|
47265
|
+
}
|
|
47266
|
+
return Boolean(symbol.declarationNode.id === symbol.bindingIdentifier && symbol.initializer && isTanstackRefetchExpression(symbol.initializer, context, null, visitedSymbolIds));
|
|
47267
|
+
};
|
|
47268
|
+
const isTanstackRefetchCall = (callExpression, context, effectCallback) => {
|
|
47269
|
+
return isTanstackRefetchExpression(callExpression.callee, context, effectCallback);
|
|
47270
|
+
};
|
|
46898
47271
|
const queryNoQueryInEffect = defineRule({
|
|
46899
47272
|
id: "query-no-query-in-effect",
|
|
46900
47273
|
title: "Query refetch inside useEffect",
|
|
@@ -46910,8 +47283,7 @@ const queryNoQueryInEffect = defineRule({
|
|
|
46910
47283
|
walkAst(callback, (child) => {
|
|
46911
47284
|
if (child !== callback && isFunctionLike$2(child) && !effectInvokedFunctions.has(child)) return false;
|
|
46912
47285
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
46913
|
-
|
|
46914
|
-
if (isNodeOfType(callee, "Identifier") && callee.name === "refetch" || isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "refetch") context.report({
|
|
47286
|
+
if (isTanstackRefetchCall(child, context, callback)) context.report({
|
|
46915
47287
|
node: child,
|
|
46916
47288
|
message: "refetch() inside useEffect duplicates work React Query already does, causing extra fetches."
|
|
46917
47289
|
});
|
|
@@ -46920,28 +47292,6 @@ const queryNoQueryInEffect = defineRule({
|
|
|
46920
47292
|
});
|
|
46921
47293
|
//#endregion
|
|
46922
47294
|
//#region src/plugin/rules/tanstack-query/query-no-rest-destructuring.ts
|
|
46923
|
-
const resolveTanstackQueryHookName = (callExpression) => {
|
|
46924
|
-
const callee = callExpression.callee;
|
|
46925
|
-
if (isNodeOfType(callee, "Identifier")) {
|
|
46926
|
-
const importBinding = getImportBindingForName(callExpression, callee.name);
|
|
46927
|
-
if (importBinding === null) return TANSTACK_QUERY_HOOKS.has(callee.name) ? callee.name : null;
|
|
46928
|
-
if (importBinding.isNamespace || !isTanstackQuerySource(importBinding.source)) return null;
|
|
46929
|
-
return importBinding.exportedName !== null && TANSTACK_QUERY_HOOKS.has(importBinding.exportedName) ? importBinding.exportedName : null;
|
|
46930
|
-
}
|
|
46931
|
-
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && TANSTACK_QUERY_HOOKS.has(callee.property.name) && isNodeOfType(callee.object, "Identifier")) {
|
|
46932
|
-
const namespaceBinding = getImportBindingForName(callExpression, callee.object.name);
|
|
46933
|
-
if (namespaceBinding?.isNamespace && isTanstackQuerySource(namespaceBinding.source)) return callee.property.name;
|
|
46934
|
-
}
|
|
46935
|
-
return null;
|
|
46936
|
-
};
|
|
46937
|
-
const resolveHookNameFromInitializer = (initializer, scopes) => {
|
|
46938
|
-
if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
|
|
46939
|
-
if (!isNodeOfType(initializer, "Identifier")) return null;
|
|
46940
|
-
const resolvedSymbol = resolveConstIdentifierAlias(initializer, scopes);
|
|
46941
|
-
if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
|
|
46942
|
-
if (!isNodeOfType(resolvedSymbol.initializer, "CallExpression")) return null;
|
|
46943
|
-
return resolveTanstackQueryHookName(resolvedSymbol.initializer);
|
|
46944
|
-
};
|
|
46945
47295
|
const queryNoRestDestructuring = defineRule({
|
|
46946
47296
|
id: "query-no-rest-destructuring",
|
|
46947
47297
|
title: "Rest destructuring on query result",
|
|
@@ -46953,7 +47303,7 @@ const queryNoRestDestructuring = defineRule({
|
|
|
46953
47303
|
if (!isNodeOfType(node.id, "ObjectPattern")) return;
|
|
46954
47304
|
if (!node.init) return;
|
|
46955
47305
|
if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
|
|
46956
|
-
const hookName =
|
|
47306
|
+
const hookName = resolveTanstackQueryHookNameFromInitializer(node.init, context.scopes);
|
|
46957
47307
|
if (!hookName) return;
|
|
46958
47308
|
context.report({
|
|
46959
47309
|
node: node.id,
|
|
@@ -55649,22 +55999,6 @@ const isInsideClassComponent = (node) => {
|
|
|
55649
55999
|
}
|
|
55650
56000
|
return false;
|
|
55651
56001
|
};
|
|
55652
|
-
const hasShortCircuitAncestor = (descendant, ancestor) => {
|
|
55653
|
-
let current = descendant.parent;
|
|
55654
|
-
while (current && current !== ancestor) {
|
|
55655
|
-
if (isNodeOfType(current, "ConditionalExpression") && (isWithinRange(descendant, current.consequent) || isWithinRange(descendant, current.alternate))) return true;
|
|
55656
|
-
if (isNodeOfType(current, "LogicalExpression") && (current.operator === "&&" || current.operator === "||" || current.operator === "??") && isWithinRange(descendant, current.right)) return true;
|
|
55657
|
-
if (isNodeOfType(current, "AssignmentPattern") && isWithinRange(descendant, current.right)) return true;
|
|
55658
|
-
current = current.parent ?? null;
|
|
55659
|
-
}
|
|
55660
|
-
return false;
|
|
55661
|
-
};
|
|
55662
|
-
const isWithinRange = (descendant, sibling) => {
|
|
55663
|
-
const descendantSpan = descendant;
|
|
55664
|
-
const siblingSpan = sibling;
|
|
55665
|
-
if (typeof descendantSpan.start !== "number" || typeof siblingSpan.start !== "number" || typeof siblingSpan.end !== "number") return false;
|
|
55666
|
-
return descendantSpan.start >= siblingSpan.start && (descendantSpan.end ?? siblingSpan.end) <= siblingSpan.end;
|
|
55667
|
-
};
|
|
55668
56002
|
const isInsideTry = (descendant, ancestor) => {
|
|
55669
56003
|
let current = descendant.parent;
|
|
55670
56004
|
while (current && current !== ancestor) {
|
|
@@ -55856,7 +56190,7 @@ const rulesOfHooks = defineRule({
|
|
|
55856
56190
|
});
|
|
55857
56191
|
return;
|
|
55858
56192
|
}
|
|
55859
|
-
if (
|
|
56193
|
+
if (isNodeConditionallyExecuted(node, enclosing.node)) {
|
|
55860
56194
|
context.report({
|
|
55861
56195
|
node: node.callee,
|
|
55862
56196
|
message: buildConditionalMessage(hookName)
|