oxlint-plugin-react-doctor 0.7.9-dev.21043e0 → 0.7.9-dev.2979a9b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +0 -46
- package/dist/index.js +23 -455
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -5503,29 +5503,6 @@ declare const REACT_DOCTOR_RULES: readonly [{
|
|
|
5503
5503
|
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
5504
5504
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5505
5505
|
};
|
|
5506
|
-
}, {
|
|
5507
|
-
readonly key: "react-doctor/no-ref-callback-cleanup-before-react-19";
|
|
5508
|
-
readonly id: "no-ref-callback-cleanup-before-react-19";
|
|
5509
|
-
readonly source: "react-doctor";
|
|
5510
|
-
readonly originallyExternal: false;
|
|
5511
|
-
readonly rule: {
|
|
5512
|
-
readonly framework: "global";
|
|
5513
|
-
readonly category: "Bugs";
|
|
5514
|
-
readonly id: string;
|
|
5515
|
-
readonly title?: string;
|
|
5516
|
-
readonly severity: RuleSeverity;
|
|
5517
|
-
readonly requires?: ReadonlyArray<Capability>;
|
|
5518
|
-
readonly disabledWhen?: ReadonlyArray<Capability>;
|
|
5519
|
-
readonly tags?: ReadonlyArray<string>;
|
|
5520
|
-
readonly matchByOccurrence?: boolean;
|
|
5521
|
-
readonly defaultEnabled?: boolean;
|
|
5522
|
-
readonly lifecycle?: "retired";
|
|
5523
|
-
readonly scan?: FileScan;
|
|
5524
|
-
readonly committedFilesOnly?: boolean;
|
|
5525
|
-
readonly recommendation?: string;
|
|
5526
|
-
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
5527
|
-
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5528
|
-
};
|
|
5529
5506
|
}, {
|
|
5530
5507
|
readonly key: "react-doctor/no-ref-current-in-render";
|
|
5531
5508
|
readonly id: "no-ref-current-in-render";
|
|
@@ -14815,29 +14792,6 @@ declare const RULES: readonly [{
|
|
|
14815
14792
|
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
14816
14793
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
14817
14794
|
};
|
|
14818
|
-
}, {
|
|
14819
|
-
readonly key: "react-doctor/no-ref-callback-cleanup-before-react-19";
|
|
14820
|
-
readonly id: "no-ref-callback-cleanup-before-react-19";
|
|
14821
|
-
readonly source: "react-doctor";
|
|
14822
|
-
readonly originallyExternal: false;
|
|
14823
|
-
readonly rule: {
|
|
14824
|
-
readonly framework: "global";
|
|
14825
|
-
readonly category: "Bugs";
|
|
14826
|
-
readonly id: string;
|
|
14827
|
-
readonly title?: string;
|
|
14828
|
-
readonly severity: RuleSeverity;
|
|
14829
|
-
readonly requires?: ReadonlyArray<Capability>;
|
|
14830
|
-
readonly disabledWhen?: ReadonlyArray<Capability>;
|
|
14831
|
-
readonly tags?: ReadonlyArray<string>;
|
|
14832
|
-
readonly matchByOccurrence?: boolean;
|
|
14833
|
-
readonly defaultEnabled?: boolean;
|
|
14834
|
-
readonly lifecycle?: "retired";
|
|
14835
|
-
readonly scan?: FileScan;
|
|
14836
|
-
readonly committedFilesOnly?: boolean;
|
|
14837
|
-
readonly recommendation?: string;
|
|
14838
|
-
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
14839
|
-
readonly create: (context: RuleContext) => RuleVisitors;
|
|
14840
|
-
};
|
|
14841
14795
|
}, {
|
|
14842
14796
|
readonly key: "react-doctor/no-ref-current-in-render";
|
|
14843
14797
|
readonly id: "no-ref-current-in-render";
|
package/dist/index.js
CHANGED
|
@@ -14947,8 +14947,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
14947
14947
|
const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
|
|
14948
14948
|
if (!releaseHandler) return releaseVerbName === "off";
|
|
14949
14949
|
const expectedHandlerKey = usesUnaryListenerSignature ? usage.eventKey : usage.handlerKey;
|
|
14950
|
-
|
|
14951
|
-
return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
|
|
14950
|
+
return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey;
|
|
14952
14951
|
}
|
|
14953
14952
|
if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
|
|
14954
14953
|
return true;
|
|
@@ -14973,25 +14972,6 @@ const isPotentiallyReachableFunction = (functionNode, context) => {
|
|
|
14973
14972
|
if (!symbol) return false;
|
|
14974
14973
|
return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
|
|
14975
14974
|
};
|
|
14976
|
-
const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
|
|
14977
|
-
if (usage.kind !== "subscribe" || usage.registrationVerbName !== "addEventListener" || usage.receiverKey === null || usage.eventKey === null || !isNodeOfType(usage.node, "CallExpression") || !isFunctionLike$1(releaseFunction) || releaseFunction.async || releaseFunction.generator || !isNodeOfType(releaseFunction.body, "BlockStatement") || !doMatchingNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseNode], context)) return false;
|
|
14978
|
-
const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
|
|
14979
|
-
const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
|
|
14980
|
-
if (!isNodeOfType(releaseCall, "CallExpression")) return false;
|
|
14981
|
-
const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
|
|
14982
|
-
if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
|
|
14983
|
-
const ownerFunction = findEnclosingFunction$1(releaseFunction);
|
|
14984
|
-
if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
|
|
14985
|
-
const triggerRegistrations = [];
|
|
14986
|
-
walkAst(ownerFunction.body, (child) => {
|
|
14987
|
-
if (child !== ownerFunction.body && isFunctionLike$1(child)) return false;
|
|
14988
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
14989
|
-
const registrationDetails = getCallRegistrationDetails(child, context);
|
|
14990
|
-
if (registrationDetails.registrationVerbName === "addEventListener" && registrationDetails.receiverKey === usage.receiverKey && resolveStableValue(child.arguments?.[1], context) === releaseFunction) triggerRegistrations.push(child);
|
|
14991
|
-
});
|
|
14992
|
-
if (triggerRegistrations.some((triggerRegistration) => triggerRegistration === usage.node)) return true;
|
|
14993
|
-
return doMatchingNodesCoverEveryPathAfterUsage(usage.node, triggerRegistrations, context) || doMatchingNodesCoverEveryPathBeforeUsage(usage.node, triggerRegistrations, ownerFunction, context);
|
|
14994
|
-
};
|
|
14995
14975
|
const isReleaseReachableForUsage = (releaseNode, usage, context) => {
|
|
14996
14976
|
if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
|
|
14997
14977
|
const releaseFunction = findEnclosingFunction$1(releaseNode);
|
|
@@ -14999,7 +14979,6 @@ const isReleaseReachableForUsage = (releaseNode, usage, context) => {
|
|
|
14999
14979
|
if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
|
|
15000
14980
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15001
14981
|
if (usageFunction && isFunctionLike$1(usageFunction) && getAssignedReactRefSymbol(usageFunction, context) && isCleanupFunctionReferencedByReturn(usageFunction, releaseFunction, context)) return isReactRefCallbackCleanupOwnedByEffect(usageFunction, releaseFunction, usage, context);
|
|
15002
|
-
if (isSelfReleasingListenerRelease(releaseNode, releaseFunction, usage, context)) return true;
|
|
15003
14982
|
return isPotentiallyReachableFunction(releaseFunction, context);
|
|
15004
14983
|
};
|
|
15005
14984
|
const fileContainsReleaseForUsage = (usage, context) => {
|
|
@@ -17133,7 +17112,7 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allow
|
|
|
17133
17112
|
keys.add(depKey);
|
|
17134
17113
|
continue;
|
|
17135
17114
|
}
|
|
17136
|
-
const identitySourceKeys =
|
|
17115
|
+
const identitySourceKeys = resolveReactiveIdentitySourceKeys(symbol, scopes);
|
|
17137
17116
|
if (identitySourceKeys) {
|
|
17138
17117
|
if (identitySourceKeys.size === 0) stableCapturedNames.add(depKey);
|
|
17139
17118
|
for (const identitySourceKey of identitySourceKeys) keys.add(identitySourceKey);
|
|
@@ -17216,161 +17195,6 @@ const resolveReactiveIdentitySourceKeys = (symbol, scopes) => {
|
|
|
17216
17195
|
if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier || symbol.references.some((reference) => reference.flag !== "read")) return null;
|
|
17217
17196
|
return resolveIdentitySourceKeysFromExpression(symbol.initializer, scopes, new Set([symbol.id]));
|
|
17218
17197
|
};
|
|
17219
|
-
const isPureDerivedExpression = (expression) => {
|
|
17220
|
-
const candidate = unwrapExpression$3(expression);
|
|
17221
|
-
if (isNodeOfType(candidate, "Literal") || isNodeOfType(candidate, "Identifier")) return true;
|
|
17222
|
-
if (isNodeOfType(candidate, "MemberExpression")) return isPureDerivedExpression(candidate.object) && (!candidate.computed || isPureDerivedExpression(candidate.property));
|
|
17223
|
-
if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return isPureDerivedExpression(candidate.left) && isPureDerivedExpression(candidate.right);
|
|
17224
|
-
if (isNodeOfType(candidate, "UnaryExpression")) return candidate.operator !== "delete" && isPureDerivedExpression(candidate.argument);
|
|
17225
|
-
if (isNodeOfType(candidate, "ConditionalExpression")) return isPureDerivedExpression(candidate.test) && isPureDerivedExpression(candidate.consequent) && isPureDerivedExpression(candidate.alternate);
|
|
17226
|
-
if (isNodeOfType(candidate, "TemplateLiteral")) return candidate.expressions.every((nestedExpression) => isPureDerivedExpression(nestedExpression));
|
|
17227
|
-
return false;
|
|
17228
|
-
};
|
|
17229
|
-
const isPureDerivedStatement = (statement) => {
|
|
17230
|
-
if (isNodeOfType(statement, "BlockStatement")) return statement.body.every((nestedStatement) => isPureDerivedStatement(nestedStatement));
|
|
17231
|
-
if (isNodeOfType(statement, "ReturnStatement")) return !statement.argument || isPureDerivedExpression(statement.argument);
|
|
17232
|
-
if (isNodeOfType(statement, "IfStatement")) return isPureDerivedExpression(statement.test) && isPureDerivedStatement(statement.consequent) && (!statement.alternate || isPureDerivedStatement(statement.alternate));
|
|
17233
|
-
return false;
|
|
17234
|
-
};
|
|
17235
|
-
const isPureDerivedFunction = (functionNode) => {
|
|
17236
|
-
if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return false;
|
|
17237
|
-
if (functionNode.async || functionNode.generator) return false;
|
|
17238
|
-
return isNodeOfType(functionNode.body, "BlockStatement") ? isPureDerivedStatement(functionNode.body) : isPureDerivedExpression(functionNode.body);
|
|
17239
|
-
};
|
|
17240
|
-
const resolvePureCalledFunctionSourceKeys = (reference, symbol, scopes) => {
|
|
17241
|
-
if (symbol.references.some((symbolReference) => symbolReference.flag !== "read")) return null;
|
|
17242
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
17243
|
-
const callExpression = referenceRoot.parent;
|
|
17244
|
-
if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== referenceRoot) return null;
|
|
17245
|
-
const functionNode = getFunctionValueNode(symbol);
|
|
17246
|
-
if (!functionNode || !isPureDerivedFunction(functionNode)) return null;
|
|
17247
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17248
|
-
for (const capturedReference of closureCaptures(functionNode, scopes)) {
|
|
17249
|
-
const capturedSymbol = capturedReference.resolvedSymbol;
|
|
17250
|
-
if (!capturedSymbol || capturedSymbol.id === symbol.id) continue;
|
|
17251
|
-
if (isOutsideAllFunctions(capturedSymbol) || symbolHasStableValue(capturedSymbol, scopes)) continue;
|
|
17252
|
-
const capturedKey = computeDepKey(capturedReference);
|
|
17253
|
-
if (!capturedKey) return null;
|
|
17254
|
-
if (capturedKey === capturedSymbol.name) {
|
|
17255
|
-
const nestedSourceKeys = resolveReactiveIdentitySourceKeys(capturedSymbol, scopes);
|
|
17256
|
-
if (nestedSourceKeys) {
|
|
17257
|
-
for (const nestedSourceKey of nestedSourceKeys) sourceKeys.add(nestedSourceKey);
|
|
17258
|
-
continue;
|
|
17259
|
-
}
|
|
17260
|
-
}
|
|
17261
|
-
sourceKeys.add(capturedKey);
|
|
17262
|
-
}
|
|
17263
|
-
return sourceKeys.size > 0 ? sourceKeys : null;
|
|
17264
|
-
};
|
|
17265
|
-
const mergeDerivedExpressionSourceKeys = (expressions, scopes, visitedSymbolIds) => {
|
|
17266
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17267
|
-
for (const expression of expressions) {
|
|
17268
|
-
const expressionSourceKeys = resolveDerivedExpressionSourceKeys(expression, scopes, visitedSymbolIds);
|
|
17269
|
-
if (!expressionSourceKeys) return null;
|
|
17270
|
-
for (const expressionSourceKey of expressionSourceKeys) sourceKeys.add(expressionSourceKey);
|
|
17271
|
-
}
|
|
17272
|
-
return sourceKeys;
|
|
17273
|
-
};
|
|
17274
|
-
const resolveDerivedExpressionSourceKeys = (expression, scopes, visitedSymbolIds) => {
|
|
17275
|
-
const candidate = unwrapExpression$3(expression);
|
|
17276
|
-
if (isNodeOfType(candidate, "Literal")) return /* @__PURE__ */ new Set();
|
|
17277
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
17278
|
-
if (scopes.isGlobalReference(candidate)) return /* @__PURE__ */ new Set();
|
|
17279
|
-
const sourceSymbol = scopes.symbolFor(candidate);
|
|
17280
|
-
if (!sourceSymbol) return null;
|
|
17281
|
-
if (isOutsideAllFunctions(sourceSymbol) || symbolHasStableValue(sourceSymbol, scopes)) return /* @__PURE__ */ new Set();
|
|
17282
|
-
if (sourceSymbol.kind === "const" && sourceSymbol.initializer && isNodeOfType(sourceSymbol.declarationNode, "VariableDeclarator") && sourceSymbol.declarationNode.id === sourceSymbol.bindingIdentifier && sourceSymbol.references.every((sourceReference) => sourceReference.flag === "read") && !visitedSymbolIds.has(sourceSymbol.id)) {
|
|
17283
|
-
visitedSymbolIds.add(sourceSymbol.id);
|
|
17284
|
-
const sourceKeys = resolveDerivedExpressionSourceKeys(sourceSymbol.initializer, scopes, visitedSymbolIds);
|
|
17285
|
-
visitedSymbolIds.delete(sourceSymbol.id);
|
|
17286
|
-
if (sourceKeys) return sourceKeys;
|
|
17287
|
-
}
|
|
17288
|
-
return new Set([sourceSymbol.name]);
|
|
17289
|
-
}
|
|
17290
|
-
if (isNodeOfType(candidate, "MemberExpression")) {
|
|
17291
|
-
if (hasComputedMemberExpression(candidate)) return null;
|
|
17292
|
-
const sourceKey = stringifyMemberChain(candidate);
|
|
17293
|
-
const rootIdentifier = getMemberRootIdentifier(candidate);
|
|
17294
|
-
const rootSymbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
|
|
17295
|
-
if (!sourceKey || !rootSymbol) return null;
|
|
17296
|
-
if (isOutsideAllFunctions(rootSymbol) || symbolHasStableValue(rootSymbol, scopes)) return /* @__PURE__ */ new Set();
|
|
17297
|
-
return new Set([sourceKey]);
|
|
17298
|
-
}
|
|
17299
|
-
if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return mergeDerivedExpressionSourceKeys([candidate.left, candidate.right], scopes, visitedSymbolIds);
|
|
17300
|
-
if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator !== "delete") return resolveDerivedExpressionSourceKeys(candidate.argument, scopes, visitedSymbolIds);
|
|
17301
|
-
if (isNodeOfType(candidate, "ConditionalExpression")) return mergeDerivedExpressionSourceKeys([
|
|
17302
|
-
candidate.test,
|
|
17303
|
-
candidate.consequent,
|
|
17304
|
-
candidate.alternate
|
|
17305
|
-
], scopes, visitedSymbolIds);
|
|
17306
|
-
if (isNodeOfType(candidate, "TemplateLiteral")) return mergeDerivedExpressionSourceKeys(candidate.expressions, scopes, visitedSymbolIds);
|
|
17307
|
-
if (isNodeOfType(candidate, "NewExpression")) {
|
|
17308
|
-
const callee = unwrapExpression$3(candidate.callee);
|
|
17309
|
-
if (!isNodeOfType(callee, "Identifier") || callee.name !== "Error" || !scopes.isGlobalReference(callee)) return null;
|
|
17310
|
-
const argumentsToAnalyze = [];
|
|
17311
|
-
for (const argument of candidate.arguments) {
|
|
17312
|
-
if (!isAstNode(argument) || isNodeOfType(argument, "SpreadElement")) return null;
|
|
17313
|
-
argumentsToAnalyze.push(argument);
|
|
17314
|
-
}
|
|
17315
|
-
return mergeDerivedExpressionSourceKeys(argumentsToAnalyze, scopes, visitedSymbolIds);
|
|
17316
|
-
}
|
|
17317
|
-
return null;
|
|
17318
|
-
};
|
|
17319
|
-
const resolveWriteControlSourceKeys = (assignment, boundaryFunction, scopes) => {
|
|
17320
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17321
|
-
let currentNode = assignment;
|
|
17322
|
-
while (currentNode.parent && currentNode.parent !== boundaryFunction) {
|
|
17323
|
-
const parentNode = currentNode.parent;
|
|
17324
|
-
if (isNodeOfType(parentNode, "IfStatement")) {
|
|
17325
|
-
if (parentNode.test === currentNode) return null;
|
|
17326
|
-
const testSourceKeys = resolveDerivedExpressionSourceKeys(parentNode.test, scopes, /* @__PURE__ */ new Set());
|
|
17327
|
-
if (!testSourceKeys) return null;
|
|
17328
|
-
for (const testSourceKey of testSourceKeys) sourceKeys.add(testSourceKey);
|
|
17329
|
-
} else if (!isNodeOfType(parentNode, "ExpressionStatement") && !isNodeOfType(parentNode, "BlockStatement")) return null;
|
|
17330
|
-
currentNode = parentNode;
|
|
17331
|
-
}
|
|
17332
|
-
return currentNode.parent === boundaryFunction ? sourceKeys : null;
|
|
17333
|
-
};
|
|
17334
|
-
const isReadOnlyInitialStateUse = (referenceNode, scopes) => {
|
|
17335
|
-
const referenceRoot = findTransparentExpressionRoot(referenceNode);
|
|
17336
|
-
const callExpression = referenceRoot.parent;
|
|
17337
|
-
return isNodeOfType(callExpression, "CallExpression") && callExpression.arguments.some((argument) => argument === referenceRoot) && isReactApiCall(callExpression, "useState", scopes, {
|
|
17338
|
-
allowGlobalReactNamespace: true,
|
|
17339
|
-
allowUnboundBareCalls: true,
|
|
17340
|
-
resolveNamedAliases: true
|
|
17341
|
-
});
|
|
17342
|
-
};
|
|
17343
|
-
const resolveRenderDerivedMutableSourceKeys = (capturedReference, symbol, scopes) => {
|
|
17344
|
-
if (symbol.kind !== "let" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
17345
|
-
const boundaryFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
|
|
17346
|
-
if (!boundaryFunction) return null;
|
|
17347
|
-
const capturingFunction = findEnclosingFunction$1(capturedReference.identifier);
|
|
17348
|
-
if (!capturingFunction || capturingFunction === boundaryFunction) return null;
|
|
17349
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17350
|
-
if (symbol.initializer) {
|
|
17351
|
-
const initializerSourceKeys = resolveDerivedExpressionSourceKeys(symbol.initializer, scopes, new Set([symbol.id]));
|
|
17352
|
-
if (!initializerSourceKeys) return null;
|
|
17353
|
-
for (const initializerSourceKey of initializerSourceKeys) sourceKeys.add(initializerSourceKey);
|
|
17354
|
-
}
|
|
17355
|
-
let writeCount = 0;
|
|
17356
|
-
for (const symbolReference of symbol.references) {
|
|
17357
|
-
if (symbolReference.flag === "read") {
|
|
17358
|
-
if (findEnclosingFunction$1(symbolReference.identifier) !== capturingFunction && !isReadOnlyInitialStateUse(symbolReference.identifier, scopes)) return null;
|
|
17359
|
-
continue;
|
|
17360
|
-
}
|
|
17361
|
-
if (symbolReference.flag !== "write") return null;
|
|
17362
|
-
const referenceRoot = findTransparentExpressionRoot(symbolReference.identifier);
|
|
17363
|
-
const assignment = referenceRoot.parent;
|
|
17364
|
-
if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== referenceRoot || findEnclosingFunction$1(referenceRoot) !== boundaryFunction) return null;
|
|
17365
|
-
const assignmentSourceKeys = resolveDerivedExpressionSourceKeys(assignment.right, scopes, new Set([symbol.id]));
|
|
17366
|
-
const controlSourceKeys = resolveWriteControlSourceKeys(assignment, boundaryFunction, scopes);
|
|
17367
|
-
if (!assignmentSourceKeys || !controlSourceKeys) return null;
|
|
17368
|
-
for (const assignmentSourceKey of assignmentSourceKeys) sourceKeys.add(assignmentSourceKey);
|
|
17369
|
-
for (const controlSourceKey of controlSourceKeys) sourceKeys.add(controlSourceKey);
|
|
17370
|
-
writeCount += 1;
|
|
17371
|
-
}
|
|
17372
|
-
return writeCount > 0 && sourceKeys.size > 0 ? sourceKeys : null;
|
|
17373
|
-
};
|
|
17374
17198
|
const isUseCallbackResultDep = (node, scopes) => {
|
|
17375
17199
|
const rootSymbol = getRootSymbol(node, scopes);
|
|
17376
17200
|
const initializer = rootSymbol?.initializer ? unwrapExpression$3(rootSymbol.initializer) : null;
|
|
@@ -41089,223 +40913,41 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
|
|
|
41089
40913
|
if (leftResult === false && rightResult === false) return false;
|
|
41090
40914
|
return null;
|
|
41091
40915
|
};
|
|
41092
|
-
const readHydrationConditionResult = (expression, context, runtime
|
|
40916
|
+
const readHydrationConditionResult = (expression, context, runtime) => {
|
|
41093
40917
|
const unwrappedExpression = stripParenExpression(expression);
|
|
41094
40918
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
41095
40919
|
if (predicateMatch) return predicateMatch[`${runtime}Result`];
|
|
41096
40920
|
const staticResult = readInitialStateBoolean(unwrappedExpression, context.scopes);
|
|
41097
40921
|
if (staticResult !== null) return staticResult;
|
|
41098
|
-
const expressionSymbol = isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression) : null;
|
|
41099
|
-
const parameterValue = expressionSymbol ? state.parameterValuesBySymbolId.get(expressionSymbol.id) : null;
|
|
41100
|
-
if (expressionSymbol && parameterValue && !state.visitedSymbolIds.has(expressionSymbol.id)) {
|
|
41101
|
-
state.visitedSymbolIds.add(expressionSymbol.id);
|
|
41102
|
-
const result = readHydrationConditionResult(parameterValue, context, runtime, state);
|
|
41103
|
-
state.visitedSymbolIds.delete(expressionSymbol.id);
|
|
41104
|
-
return result;
|
|
41105
|
-
}
|
|
41106
|
-
if (expressionSymbol && expressionSymbol.kind === "const" && expressionSymbol.initializer && expressionSymbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(expressionSymbol.id)) {
|
|
41107
|
-
state.visitedSymbolIds.add(expressionSymbol.id);
|
|
41108
|
-
const result = readHydrationConditionResult(expressionSymbol.initializer, context, runtime, state);
|
|
41109
|
-
state.visitedSymbolIds.delete(expressionSymbol.id);
|
|
41110
|
-
return result;
|
|
41111
|
-
}
|
|
41112
|
-
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
41113
|
-
const callArguments = unwrappedExpression.arguments ?? [];
|
|
41114
|
-
if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
|
|
41115
|
-
allowGlobalReactNamespace: true,
|
|
41116
|
-
resolveNamedAliases: true
|
|
41117
|
-
})) {
|
|
41118
|
-
const callbackArgument = callArguments[0];
|
|
41119
|
-
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
|
|
41120
|
-
const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
|
|
41121
|
-
return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? readHydrationFunctionResult(callbackFunction, context, runtime, state) : null;
|
|
41122
|
-
}
|
|
41123
|
-
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
41124
|
-
if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return readHydrationConditionResult(callArguments[0], context, runtime, state);
|
|
41125
|
-
const helperFunction = resolveExactLocalFunction(callee, context.scopes);
|
|
41126
|
-
if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
|
|
41127
|
-
const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
|
|
41128
|
-
for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
|
|
41129
|
-
const parameter = helperFunction.params[parameterIndex];
|
|
41130
|
-
const argument = callArguments[parameterIndex];
|
|
41131
|
-
if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
|
|
41132
|
-
const parameterSymbol = context.scopes.symbolFor(parameter);
|
|
41133
|
-
if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
|
|
41134
|
-
}
|
|
41135
|
-
return readHydrationFunctionResult(helperFunction, context, runtime, {
|
|
41136
|
-
...state,
|
|
41137
|
-
parameterValuesBySymbolId
|
|
41138
|
-
});
|
|
41139
|
-
}
|
|
41140
40922
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
|
|
41141
|
-
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime
|
|
40923
|
+
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime);
|
|
41142
40924
|
return argumentResult === null ? null : !argumentResult;
|
|
41143
40925
|
}
|
|
41144
40926
|
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
41145
|
-
return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime
|
|
40927
|
+
return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime), readHydrationConditionResult(unwrappedExpression.right, context, runtime));
|
|
41146
40928
|
};
|
|
41147
|
-
const
|
|
41148
|
-
if (isNodeOfType(statement, "ReturnStatement")) return {
|
|
41149
|
-
didReturn: true,
|
|
41150
|
-
value: statement.argument ? readHydrationConditionResult(statement.argument, context, runtime, state) : null
|
|
41151
|
-
};
|
|
41152
|
-
if (isNodeOfType(statement, "BlockStatement")) {
|
|
41153
|
-
for (const childStatement of statement.body) {
|
|
41154
|
-
const result = readHydrationStatementResult(childStatement, context, runtime, state);
|
|
41155
|
-
if (result.didReturn) return result;
|
|
41156
|
-
if (statementAlwaysExits(childStatement)) break;
|
|
41157
|
-
}
|
|
41158
|
-
return {
|
|
41159
|
-
didReturn: false,
|
|
41160
|
-
value: null
|
|
41161
|
-
};
|
|
41162
|
-
}
|
|
41163
|
-
if (!isNodeOfType(statement, "IfStatement")) return {
|
|
41164
|
-
didReturn: false,
|
|
41165
|
-
value: null
|
|
41166
|
-
};
|
|
41167
|
-
const conditionResult = readHydrationConditionResult(statement.test, context, runtime, state);
|
|
41168
|
-
if (conditionResult !== null) {
|
|
41169
|
-
const selectedBranch = conditionResult ? statement.consequent : statement.alternate;
|
|
41170
|
-
return selectedBranch ? readHydrationStatementResult(selectedBranch, context, runtime, state) : {
|
|
41171
|
-
didReturn: false,
|
|
41172
|
-
value: null
|
|
41173
|
-
};
|
|
41174
|
-
}
|
|
41175
|
-
const consequentResult = readHydrationStatementResult(statement.consequent, context, runtime, state);
|
|
41176
|
-
const alternateResult = statement.alternate ? readHydrationStatementResult(statement.alternate, context, runtime, state) : {
|
|
41177
|
-
didReturn: false,
|
|
41178
|
-
value: null
|
|
41179
|
-
};
|
|
41180
|
-
return consequentResult.didReturn && alternateResult.didReturn && consequentResult.value !== null && consequentResult.value === alternateResult.value ? consequentResult : {
|
|
41181
|
-
didReturn: consequentResult.didReturn || alternateResult.didReturn,
|
|
41182
|
-
value: null
|
|
41183
|
-
};
|
|
41184
|
-
};
|
|
41185
|
-
const readHydrationFunctionResult = (functionNode, context, runtime, state) => {
|
|
41186
|
-
if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
|
|
41187
|
-
state.visitedFunctionNodes.add(functionNode);
|
|
41188
|
-
const result = isNodeOfType(functionNode.body, "BlockStatement") ? readHydrationStatementResult(functionNode.body, context, runtime, state).value : readHydrationConditionResult(functionNode.body, context, runtime, state);
|
|
41189
|
-
state.visitedFunctionNodes.delete(functionNode);
|
|
41190
|
-
return result;
|
|
41191
|
-
};
|
|
41192
|
-
const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, scopes) => {
|
|
41193
|
-
const left = stripParenExpression(leftExpression);
|
|
41194
|
-
const right = stripParenExpression(rightExpression);
|
|
41195
|
-
if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
|
|
41196
|
-
const leftSymbol = scopes.symbolFor(left);
|
|
41197
|
-
const rightSymbol = scopes.symbolFor(right);
|
|
41198
|
-
return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
|
|
41199
|
-
}
|
|
41200
|
-
if (isNodeOfType(left, "MemberExpression") && isNodeOfType(right, "MemberExpression")) return doEquivalentExpressionBindingsMatch(left.object, right.object, scopes) && (!left.computed || doEquivalentExpressionBindingsMatch(left.property, right.property, scopes));
|
|
41201
|
-
if (isNodeOfType(left, "CallExpression") && isNodeOfType(right, "CallExpression")) {
|
|
41202
|
-
const rightArguments = right.arguments ?? [];
|
|
41203
|
-
return doEquivalentExpressionBindingsMatch(left.callee, right.callee, scopes) && (left.arguments ?? []).every((argument, index) => {
|
|
41204
|
-
const rightArgument = rightArguments[index];
|
|
41205
|
-
return Boolean(rightArgument && doEquivalentExpressionBindingsMatch(argument, rightArgument, scopes));
|
|
41206
|
-
});
|
|
41207
|
-
}
|
|
41208
|
-
return true;
|
|
41209
|
-
};
|
|
41210
|
-
const areHelperReturnValuesEquivalent = (leftValue, rightValue, context) => {
|
|
41211
|
-
if (areExpressionsStructurallyEqual(leftValue, rightValue)) return doEquivalentExpressionBindingsMatch(leftValue, rightValue, context.scopes);
|
|
41212
|
-
const leftBoolean = readInitialStateBoolean(leftValue, context.scopes);
|
|
41213
|
-
const rightBoolean = readInitialStateBoolean(rightValue, context.scopes);
|
|
41214
|
-
return leftBoolean !== null && rightBoolean !== null && leftBoolean === rightBoolean;
|
|
41215
|
-
};
|
|
41216
|
-
const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
|
|
41217
|
-
const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
|
|
41218
|
-
return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
|
|
41219
|
-
};
|
|
41220
|
-
const matchHydrationConditionInternal = (expression, context, state) => {
|
|
40929
|
+
const matchHydrationCondition = (expression, context) => {
|
|
41221
40930
|
const unwrappedExpression = stripParenExpression(expression);
|
|
41222
40931
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
41223
40932
|
if (predicateMatch) return {
|
|
41224
40933
|
predicateMatch,
|
|
41225
40934
|
predicateNode: unwrappedExpression
|
|
41226
40935
|
};
|
|
41227
|
-
if (isNodeOfType(unwrappedExpression, "
|
|
41228
|
-
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
41229
|
-
const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
|
|
41230
|
-
if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
|
|
41231
|
-
state.visitedSymbolIds.add(symbol.id);
|
|
41232
|
-
const match = matchHydrationConditionInternal(parameterValue, context, state);
|
|
41233
|
-
state.visitedSymbolIds.delete(symbol.id);
|
|
41234
|
-
return match;
|
|
41235
|
-
}
|
|
41236
|
-
if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
|
|
41237
|
-
state.visitedSymbolIds.add(symbol.id);
|
|
41238
|
-
const match = matchHydrationConditionInternal(symbol.initializer, context, state);
|
|
41239
|
-
state.visitedSymbolIds.delete(symbol.id);
|
|
41240
|
-
return match;
|
|
41241
|
-
}
|
|
41242
|
-
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
41243
|
-
const callArguments = unwrappedExpression.arguments ?? [];
|
|
41244
|
-
if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
|
|
41245
|
-
allowGlobalReactNamespace: true,
|
|
41246
|
-
resolveNamedAliases: true
|
|
41247
|
-
})) {
|
|
41248
|
-
const callbackArgument = callArguments[0];
|
|
41249
|
-
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
|
|
41250
|
-
const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
|
|
41251
|
-
return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionResult(callbackFunction, context, state) : null;
|
|
41252
|
-
}
|
|
41253
|
-
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
41254
|
-
if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return matchHydrationConditionInternal(callArguments[0], context, state);
|
|
41255
|
-
const helperFunction = resolveExactLocalFunction(callee, context.scopes);
|
|
41256
|
-
if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
|
|
41257
|
-
const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
|
|
41258
|
-
for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
|
|
41259
|
-
const parameter = helperFunction.params[parameterIndex];
|
|
41260
|
-
const argument = callArguments[parameterIndex];
|
|
41261
|
-
if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
|
|
41262
|
-
const parameterSymbol = context.scopes.symbolFor(parameter);
|
|
41263
|
-
if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
|
|
41264
|
-
}
|
|
41265
|
-
return matchHydrationFunctionResult(helperFunction, context, {
|
|
41266
|
-
...state,
|
|
41267
|
-
parameterValuesBySymbolId
|
|
41268
|
-
});
|
|
41269
|
-
}
|
|
41270
|
-
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
|
|
40936
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationCondition(unwrappedExpression.argument, context);
|
|
41271
40937
|
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
41272
|
-
const leftMatch =
|
|
41273
|
-
const rightMatch =
|
|
40938
|
+
const leftMatch = matchHydrationCondition(unwrappedExpression.left, context);
|
|
40939
|
+
const rightMatch = matchHydrationCondition(unwrappedExpression.right, context);
|
|
40940
|
+
if (leftMatch && rightMatch) {
|
|
40941
|
+
const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client");
|
|
40942
|
+
const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server");
|
|
40943
|
+
return clientResult !== null && serverResult !== null && clientResult !== serverResult ? leftMatch : null;
|
|
40944
|
+
}
|
|
41274
40945
|
const nestedMatch = leftMatch ?? rightMatch;
|
|
41275
40946
|
if (!nestedMatch) return null;
|
|
41276
|
-
const
|
|
41277
|
-
|
|
41278
|
-
return
|
|
41279
|
-
};
|
|
41280
|
-
const matchHydrationReturningStatement = (statement, context, state) => {
|
|
41281
|
-
if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? matchHydrationConditionInternal(statement.argument, context, state) : null;
|
|
41282
|
-
if (isNodeOfType(statement, "IfStatement")) {
|
|
41283
|
-
const conditionMatch = matchHydrationConditionInternal(statement.test, context, state);
|
|
41284
|
-
const consequentValues = getReturnedValues(statement.consequent);
|
|
41285
|
-
const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
|
|
41286
|
-
if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
|
|
41287
|
-
return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
|
|
41288
|
-
}
|
|
41289
|
-
if (!isNodeOfType(statement, "BlockStatement")) return null;
|
|
41290
|
-
for (const childStatement of statement.body) {
|
|
41291
|
-
const match = matchHydrationReturningStatement(childStatement, context, state);
|
|
41292
|
-
if (match) return match;
|
|
41293
|
-
if (statementAlwaysExits(childStatement)) break;
|
|
41294
|
-
}
|
|
41295
|
-
return null;
|
|
40947
|
+
const otherResult = readInitialStateBoolean(leftMatch ? unwrappedExpression.right : unwrappedExpression.left, context.scopes);
|
|
40948
|
+
if (unwrappedExpression.operator === "&&" && otherResult === false || unwrappedExpression.operator === "||" && otherResult === true) return null;
|
|
40949
|
+
return nestedMatch;
|
|
41296
40950
|
};
|
|
41297
|
-
const matchHydrationFunctionResult = (functionNode, context, state) => {
|
|
41298
|
-
if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
|
|
41299
|
-
state.visitedFunctionNodes.add(functionNode);
|
|
41300
|
-
const match = isNodeOfType(functionNode.body, "BlockStatement") ? matchHydrationReturningStatement(functionNode.body, context, state) : matchHydrationConditionInternal(functionNode.body, context, state);
|
|
41301
|
-
state.visitedFunctionNodes.delete(functionNode);
|
|
41302
|
-
return match;
|
|
41303
|
-
};
|
|
41304
|
-
const matchHydrationCondition = (expression, context) => matchHydrationConditionInternal(expression, context, {
|
|
41305
|
-
parameterValuesBySymbolId: /* @__PURE__ */ new Map(),
|
|
41306
|
-
visitedFunctionNodes: /* @__PURE__ */ new Set(),
|
|
41307
|
-
visitedSymbolIds: /* @__PURE__ */ new Set()
|
|
41308
|
-
});
|
|
41309
40951
|
const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
|
|
41310
40952
|
const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
|
|
41311
40953
|
if (!leftNode || !rightNode) return leftNode === rightNode;
|
|
@@ -41448,17 +41090,17 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
41448
41090
|
const { predicateMatch, predicateNode } = conditionMatch;
|
|
41449
41091
|
if (reportedNodes.has(predicateNode)) return;
|
|
41450
41092
|
if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
|
|
41451
|
-
const componentOrHookNode = findRenderPhaseComponentOrHook(
|
|
41093
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(predicateNode, context.scopes);
|
|
41452
41094
|
if (!componentOrHookNode) return;
|
|
41453
41095
|
if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
|
|
41454
|
-
if (requiresRenderedContext && !isInRenderedOutput(
|
|
41096
|
+
if (requiresRenderedContext && !isInRenderedOutput(predicateNode, componentOrHookNode, context.scopes)) return;
|
|
41455
41097
|
if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
|
|
41456
|
-
const attribute = findEnclosingJsxAttribute(
|
|
41098
|
+
const attribute = findEnclosingJsxAttribute(predicateNode);
|
|
41457
41099
|
if (!attribute || isEventHandlerAttribute(attribute)) return;
|
|
41458
41100
|
}
|
|
41459
|
-
if (fileIsEmailTemplate || isGatedByFalsyInitialState(
|
|
41460
|
-
if (isAfterClientOnlyEarlyReturn(
|
|
41461
|
-
const openingElement = findEnclosingJsxOpeningElement(
|
|
41101
|
+
if (fileIsEmailTemplate || isGatedByFalsyInitialState(predicateNode, context.scopes)) return;
|
|
41102
|
+
if (isAfterClientOnlyEarlyReturn(predicateNode, componentOrHookNode, context.scopes)) return;
|
|
41103
|
+
const openingElement = findEnclosingJsxOpeningElement(predicateNode);
|
|
41462
41104
|
if (hasSuppressHydrationWarningAttribute(openingElement) && !isStructuralRenderedValue(leftBranch) && !isStructuralRenderedValue(rightBranch)) return;
|
|
41463
41105
|
if (branchRootsSuppressSameElement(leftBranch, rightBranch)) return;
|
|
41464
41106
|
if (isGeneratedImageRenderContext(context, openingElement ?? leftBranch)) return;
|
|
@@ -47444,69 +47086,6 @@ const noRedundantShouldComponentUpdate = defineRule({
|
|
|
47444
47086
|
}
|
|
47445
47087
|
});
|
|
47446
47088
|
//#endregion
|
|
47447
|
-
//#region src/plugin/rules/correctness/no-ref-callback-cleanup-before-react-19.ts
|
|
47448
|
-
const resolveFunctionExpressions = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
47449
|
-
const expression = stripParenExpression(rawExpression);
|
|
47450
|
-
if (isFunctionLike$1(expression)) return expression.async || expression.generator ? [] : [expression];
|
|
47451
|
-
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
47452
|
-
if (isNodeOfType(expression.test, "Literal")) return resolveFunctionExpressions(expression.test.value ? expression.consequent : expression.alternate, scopes, visitedSymbolIds);
|
|
47453
|
-
return [...resolveFunctionExpressions(expression.consequent, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.alternate, scopes, visitedSymbolIds)];
|
|
47454
|
-
}
|
|
47455
|
-
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
47456
|
-
if (isNodeOfType(expression.left, "Literal")) {
|
|
47457
|
-
const isLeftTruthy = Boolean(expression.left.value);
|
|
47458
|
-
if (expression.operator === "&&" && !isLeftTruthy) return [];
|
|
47459
|
-
if (expression.operator === "||" && isLeftTruthy) return [];
|
|
47460
|
-
if (expression.operator === "??" && expression.left.value !== null) return [];
|
|
47461
|
-
}
|
|
47462
|
-
if (expression.operator === "&&") return resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds);
|
|
47463
|
-
return [...resolveFunctionExpressions(expression.left, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds)];
|
|
47464
|
-
}
|
|
47465
|
-
if (isNodeOfType(expression, "SequenceExpression")) {
|
|
47466
|
-
const finalExpression = expression.expressions.at(-1);
|
|
47467
|
-
return finalExpression ? resolveFunctionExpressions(finalExpression, scopes, visitedSymbolIds) : [];
|
|
47468
|
-
}
|
|
47469
|
-
if (isNodeOfType(expression, "CallExpression")) {
|
|
47470
|
-
if (!isReactApiCall(expression, "useCallback", scopes)) return [];
|
|
47471
|
-
const callback = expression.arguments[0];
|
|
47472
|
-
return callback && !isNodeOfType(callback, "SpreadElement") ? resolveFunctionExpressions(callback, scopes, visitedSymbolIds) : [];
|
|
47473
|
-
}
|
|
47474
|
-
if (!isNodeOfType(expression, "Identifier")) return [];
|
|
47475
|
-
const symbol = scopes.symbolFor(expression);
|
|
47476
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return [];
|
|
47477
|
-
if (symbol.kind === "function" && isNodeOfType(symbol.declarationNode, "FunctionDeclaration") && symbol.references.every((reference) => reference.flag === "read")) return resolveFunctionExpressions(symbol.declarationNode, scopes, new Set([...visitedSymbolIds, symbol.id]));
|
|
47478
|
-
const initializer = getDirectConstInitializer(symbol);
|
|
47479
|
-
if (!initializer) return [];
|
|
47480
|
-
return resolveFunctionExpressions(initializer, scopes, new Set([...visitedSymbolIds, symbol.id]));
|
|
47481
|
-
};
|
|
47482
|
-
const functionReturnsCleanupFunction = (functionExpression, scopes) => {
|
|
47483
|
-
if (!isFunctionLike$1(functionExpression)) return false;
|
|
47484
|
-
if (!isNodeOfType(functionExpression.body, "BlockStatement")) return resolveFunctionExpressions(functionExpression.body, scopes).length > 0;
|
|
47485
|
-
return collectFunctionReturnStatements(functionExpression).some((returnStatement) => Boolean(returnStatement.argument && resolveFunctionExpressions(returnStatement.argument, scopes).length > 0));
|
|
47486
|
-
};
|
|
47487
|
-
const callbackReturnsCleanupFunction = (callback, scopes) => {
|
|
47488
|
-
return resolveFunctionExpressions(callback, scopes).some((functionExpression) => functionReturnsCleanupFunction(functionExpression, scopes));
|
|
47489
|
-
};
|
|
47490
|
-
const noRefCallbackCleanupBeforeReact19 = defineRule({
|
|
47491
|
-
id: "no-ref-callback-cleanup-before-react-19",
|
|
47492
|
-
title: "Ref cleanup requires React 19",
|
|
47493
|
-
requires: ["react:18"],
|
|
47494
|
-
disabledWhen: ["react:19"],
|
|
47495
|
-
severity: "warn",
|
|
47496
|
-
recommendation: "React 18 ignores functions returned from ref callbacks. Handle cleanup when React calls the ref with `null`, or require React 19 before returning a cleanup function.",
|
|
47497
|
-
create: (context) => ({ JSXAttribute(node) {
|
|
47498
|
-
if (getJsxAttributeName(node.name) !== "ref") return;
|
|
47499
|
-
if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
|
|
47500
|
-
const callback = node.value.expression;
|
|
47501
|
-
if (!callback || isNodeOfType(callback, "JSXEmptyExpression")) return;
|
|
47502
|
-
if (!callbackReturnsCleanupFunction(callback, context.scopes)) return;
|
|
47503
|
-
context.report({
|
|
47504
|
-
node,
|
|
47505
|
-
message: "This ref callback returns a cleanup function, but React 18 ignores ref cleanup returns, so the cleanup never runs. Handle detachment when React calls the ref with `null`, or require React 19."
|
|
47506
|
-
});
|
|
47507
|
-
} })
|
|
47508
|
-
});
|
|
47509
|
-
//#endregion
|
|
47510
47089
|
//#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
|
|
47511
47090
|
const REPEATED_ANCESTOR_TYPES = new Set([
|
|
47512
47091
|
"DoWhileStatement",
|
|
@@ -72274,17 +71853,6 @@ const reactDoctorRules = [
|
|
|
72274
71853
|
requires: [...new Set(["react", ...noRedundantShouldComponentUpdate.requires ?? []])]
|
|
72275
71854
|
}
|
|
72276
71855
|
},
|
|
72277
|
-
{
|
|
72278
|
-
key: "react-doctor/no-ref-callback-cleanup-before-react-19",
|
|
72279
|
-
id: "no-ref-callback-cleanup-before-react-19",
|
|
72280
|
-
source: "react-doctor",
|
|
72281
|
-
originallyExternal: false,
|
|
72282
|
-
rule: {
|
|
72283
|
-
...noRefCallbackCleanupBeforeReact19,
|
|
72284
|
-
framework: "global",
|
|
72285
|
-
category: "Bugs"
|
|
72286
|
-
}
|
|
72287
|
-
},
|
|
72288
71856
|
{
|
|
72289
71857
|
key: "react-doctor/no-ref-current-in-render",
|
|
72290
71858
|
id: "no-ref-current-in-render",
|