oxlint-plugin-react-doctor 0.7.9-dev.b51022f → 0.7.9-dev.bd3655c
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 +875 -109
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7637,6 +7637,7 @@ const areExpressionsStructurallyEqual = (a, b) => {
|
|
|
7637
7637
|
if (a.type !== b.type) return false;
|
|
7638
7638
|
if (isNodeOfType(a, "ThisExpression")) return true;
|
|
7639
7639
|
if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
|
|
7640
|
+
if (isNodeOfType(a, "PrivateIdentifier") && isNodeOfType(b, "PrivateIdentifier")) return a.name === b.name;
|
|
7640
7641
|
if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
|
|
7641
7642
|
if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
|
|
7642
7643
|
if (a.computed !== b.computed) return false;
|
|
@@ -8958,7 +8959,7 @@ const isReactNamespaceImport = (identifier, scopes) => {
|
|
|
8958
8959
|
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
8959
8960
|
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
8960
8961
|
};
|
|
8961
|
-
const isReactNamespaceReceiver = (receiver, scopes, options) => {
|
|
8962
|
+
const isReactNamespaceReceiver$1 = (receiver, scopes, options) => {
|
|
8962
8963
|
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
8963
8964
|
if (isReactNamespaceImport(receiver, scopes)) return true;
|
|
8964
8965
|
return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
|
|
@@ -8971,7 +8972,7 @@ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) =>
|
|
|
8971
8972
|
for (const property of pattern.properties) {
|
|
8972
8973
|
if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
|
|
8973
8974
|
const propertyName = getStaticPropertyKeyName(property);
|
|
8974
|
-
return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver(stripParenExpression(symbol.initializer), scopes, options));
|
|
8975
|
+
return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver$1(stripParenExpression(symbol.initializer), scopes, options));
|
|
8975
8976
|
}
|
|
8976
8977
|
return false;
|
|
8977
8978
|
};
|
|
@@ -8995,7 +8996,7 @@ const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds
|
|
|
8995
8996
|
return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
|
|
8996
8997
|
}
|
|
8997
8998
|
if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
|
|
8998
|
-
return isReactNamespaceReceiver(stripParenExpression(callee.object), scopes, options);
|
|
8999
|
+
return isReactNamespaceReceiver$1(stripParenExpression(callee.object), scopes, options);
|
|
8999
9000
|
};
|
|
9000
9001
|
//#endregion
|
|
9001
9002
|
//#region src/plugin/utils/is-proven-browser-api-receiver.ts
|
|
@@ -29421,7 +29422,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
29421
29422
|
//#endregion
|
|
29422
29423
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
29423
29424
|
const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
29424
|
-
const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
29425
|
+
const isUndefinedIdentifier$1 = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
29425
29426
|
const noAccessKey = defineRule({
|
|
29426
29427
|
id: "no-access-key",
|
|
29427
29428
|
title: "accessKey attribute used",
|
|
@@ -29446,7 +29447,7 @@ const noAccessKey = defineRule({
|
|
|
29446
29447
|
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
29447
29448
|
const expression = attributeValue.expression;
|
|
29448
29449
|
if (!expression || expression.type === "JSXEmptyExpression") return;
|
|
29449
|
-
if (isUndefinedIdentifier(expression)) return;
|
|
29450
|
+
if (isUndefinedIdentifier$1(expression)) return;
|
|
29450
29451
|
context.report({
|
|
29451
29452
|
node: accessKey,
|
|
29452
29453
|
message: MESSAGE$39
|
|
@@ -30211,6 +30212,12 @@ const isReactNamespaceImportReference = (ref) => Boolean(ref?.resolved?.defs.som
|
|
|
30211
30212
|
const importDeclaration = declarationNode.parent;
|
|
30212
30213
|
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
|
|
30213
30214
|
}));
|
|
30215
|
+
const isReactNamespaceReceiver = (analysis, node) => {
|
|
30216
|
+
const receiver = stripParenExpression(node);
|
|
30217
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
30218
|
+
const namespaceReference = getRef(analysis, receiver);
|
|
30219
|
+
return namespaceReference?.resolved ? isReactNamespaceImportReference(namespaceReference) : receiver.name === "React";
|
|
30220
|
+
};
|
|
30214
30221
|
const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
|
|
30215
30222
|
if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) return false;
|
|
30216
30223
|
const callee = stripParenExpression(declarator.init.callee);
|
|
@@ -30219,24 +30226,20 @@ const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
|
|
|
30219
30226
|
if (!reference?.resolved) return callee.name === hookName;
|
|
30220
30227
|
return isReactNamedImportReference(reference, hookName);
|
|
30221
30228
|
}
|
|
30222
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.
|
|
30223
|
-
|
|
30224
|
-
if (!namespaceReference?.resolved) return callee.object.name === "React";
|
|
30225
|
-
return isReactNamespaceImportReference(namespaceReference);
|
|
30229
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
|
|
30230
|
+
return isReactNamespaceReceiver(analysis, callee.object);
|
|
30226
30231
|
};
|
|
30227
30232
|
const isHookCallee$1 = (analysis, node, hookName) => {
|
|
30228
30233
|
if (!node) return false;
|
|
30229
30234
|
if (isNodeOfType(node, "Identifier")) {
|
|
30230
30235
|
if (node.name === hookName) return true;
|
|
30231
30236
|
if (isReactNamedImportReference(getRef(analysis, node), hookName)) return true;
|
|
30232
|
-
const
|
|
30233
|
-
|
|
30237
|
+
const receiverRoot = findTransparentExpressionRoot(node);
|
|
30238
|
+
const parent = receiverRoot.parent;
|
|
30239
|
+
if (parent && isNodeOfType(parent, "MemberExpression") && parent.object === receiverRoot && isReactNamespaceReceiver(analysis, node) && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
|
|
30234
30240
|
return false;
|
|
30235
30241
|
}
|
|
30236
|
-
if (isNodeOfType(node, "MemberExpression"))
|
|
30237
|
-
const receiver = stripParenExpression(node.object);
|
|
30238
|
-
return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
|
|
30239
|
-
}
|
|
30242
|
+
if (isNodeOfType(node, "MemberExpression")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
|
|
30240
30243
|
return false;
|
|
30241
30244
|
};
|
|
30242
30245
|
const isUseEffect = (node) => {
|
|
@@ -30660,7 +30663,88 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
|
|
|
30660
30663
|
if (HANDLER_BINDING_NAME_PATTERN.test(bindingName)) return true;
|
|
30661
30664
|
return isSetterWiredToJsxHandler(componentFunction, bindingName);
|
|
30662
30665
|
};
|
|
30663
|
-
const
|
|
30666
|
+
const isSynchronousFunction = (functionNode) => {
|
|
30667
|
+
const functionMetadata = functionNode;
|
|
30668
|
+
return functionMetadata.async !== true && functionMetadata.generator !== true;
|
|
30669
|
+
};
|
|
30670
|
+
const findBindingVariable = (analysis, bindingIdentifier) => {
|
|
30671
|
+
for (const scope of analysis.scopeManager.scopes) for (const variable of scope.variables) if (variable.identifiers.includes(bindingIdentifier)) return variable;
|
|
30672
|
+
return null;
|
|
30673
|
+
};
|
|
30674
|
+
const getImmutableFunctionVariable = (analysis, componentFunction, functionNode) => {
|
|
30675
|
+
if (!isSynchronousFunction(functionNode) || !isAstDescendant(functionNode, componentFunction)) return null;
|
|
30676
|
+
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
30677
|
+
if (!bindingIdentifier) return null;
|
|
30678
|
+
const variable = findBindingVariable(analysis, bindingIdentifier);
|
|
30679
|
+
if (!variable || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && !reference.init)) return null;
|
|
30680
|
+
const definition = variable.defs[0];
|
|
30681
|
+
if (definition.type === "FunctionName") return definition.node === functionNode ? variable : null;
|
|
30682
|
+
if (definition.type !== "Variable") return null;
|
|
30683
|
+
const declarator = definition.node;
|
|
30684
|
+
if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return null;
|
|
30685
|
+
if (declarator.init === functionNode) return variable;
|
|
30686
|
+
if (isNodeOfType(declarator.init, "CallExpression") && declarator.init.arguments?.[0] === functionNode && isGenuineReactHookDeclarator(analysis, declarator, "useCallback")) return variable;
|
|
30687
|
+
return null;
|
|
30688
|
+
};
|
|
30689
|
+
const getJsxEventValueAttribute = (identifier) => {
|
|
30690
|
+
const expression = findTransparentExpressionRoot(identifier);
|
|
30691
|
+
const expressionContainer = expression.parent;
|
|
30692
|
+
if (!isNodeOfType(expressionContainer, "JSXExpressionContainer") || expressionContainer.expression !== expression) return null;
|
|
30693
|
+
const attribute = expressionContainer.parent;
|
|
30694
|
+
if (!isNodeOfType(attribute, "JSXAttribute")) return null;
|
|
30695
|
+
const attributeName = getJsxAttributeName(attribute.name);
|
|
30696
|
+
return attributeName && isEventHandlerName(attributeName) ? attribute : null;
|
|
30697
|
+
};
|
|
30698
|
+
const getInlineJsxEventCallbackAttribute = (callExpression) => {
|
|
30699
|
+
const callbackFunction = findEnclosingFunction$1(callExpression);
|
|
30700
|
+
if (!callbackFunction || !isSynchronousFunction(callbackFunction)) return null;
|
|
30701
|
+
return getJsxEventValueAttribute(callbackFunction);
|
|
30702
|
+
};
|
|
30703
|
+
const isReactHookDependencyReference = (identifier) => {
|
|
30704
|
+
const expression = findTransparentExpressionRoot(identifier);
|
|
30705
|
+
const dependencyArray = expression.parent;
|
|
30706
|
+
if (!isNodeOfType(dependencyArray, "ArrayExpression") || !(dependencyArray.elements ?? []).includes(expression)) return false;
|
|
30707
|
+
const hookCall = dependencyArray.parent;
|
|
30708
|
+
if (!isNodeOfType(hookCall, "CallExpression") || hookCall.arguments?.[1] !== dependencyArray) return false;
|
|
30709
|
+
const callee = hookCall.callee;
|
|
30710
|
+
if (isNodeOfType(callee, "Identifier")) return /^use[A-Z0-9]/.test(callee.name);
|
|
30711
|
+
return Boolean(isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && /^use[A-Z0-9]/.test(callee.property.name));
|
|
30712
|
+
};
|
|
30713
|
+
const hasReachableJsxEventCallPath = (analysis, context, componentFunction, functionVariable, visitedVariables) => {
|
|
30714
|
+
if (visitedVariables.has(functionVariable)) return false;
|
|
30715
|
+
const nextVisitedVariables = new Set(visitedVariables).add(functionVariable);
|
|
30716
|
+
const callExpressions = [];
|
|
30717
|
+
let hasDirectJsxEventReference = false;
|
|
30718
|
+
for (const reference of functionVariable.references) {
|
|
30719
|
+
if (reference.init) continue;
|
|
30720
|
+
const identifier = reference.identifier;
|
|
30721
|
+
if (reference.isWrite()) return false;
|
|
30722
|
+
const jsxEventValueAttribute = getJsxEventValueAttribute(identifier);
|
|
30723
|
+
if (jsxEventValueAttribute) {
|
|
30724
|
+
if (isNodeReachableWithinFunction(jsxEventValueAttribute, context)) hasDirectJsxEventReference = true;
|
|
30725
|
+
continue;
|
|
30726
|
+
}
|
|
30727
|
+
if (isReactHookDependencyReference(identifier)) continue;
|
|
30728
|
+
const callExpression = getCallExpr(reference);
|
|
30729
|
+
if (!callExpression) return false;
|
|
30730
|
+
const jsxEventCallbackAttribute = getInlineJsxEventCallbackAttribute(callExpression);
|
|
30731
|
+
if (jsxEventCallbackAttribute) {
|
|
30732
|
+
if (isNodeReachableWithinFunction(callExpression, context) && isNodeReachableWithinFunction(jsxEventCallbackAttribute, context)) hasDirectJsxEventReference = true;
|
|
30733
|
+
continue;
|
|
30734
|
+
}
|
|
30735
|
+
callExpressions.push(callExpression);
|
|
30736
|
+
}
|
|
30737
|
+
if (hasDirectJsxEventReference) return true;
|
|
30738
|
+
for (const callExpression of callExpressions) {
|
|
30739
|
+
if (!isNodeReachableWithinFunction(callExpression, context)) continue;
|
|
30740
|
+
const callerFunction = findEnclosingFunction$1(callExpression);
|
|
30741
|
+
if (!callerFunction || callerFunction === componentFunction) continue;
|
|
30742
|
+
const callerVariable = getImmutableFunctionVariable(analysis, componentFunction, callerFunction);
|
|
30743
|
+
if (callerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, callerVariable, nextVisitedVariables)) return true;
|
|
30744
|
+
}
|
|
30745
|
+
return false;
|
|
30746
|
+
};
|
|
30747
|
+
const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, includeDeferredWriters = false) => {
|
|
30664
30748
|
if (!setterRef.resolved) return false;
|
|
30665
30749
|
const componentFunction = findEnclosingFunction$1(effectNode);
|
|
30666
30750
|
if (!componentFunction) return false;
|
|
@@ -30669,6 +30753,11 @@ const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters
|
|
|
30669
30753
|
const identifier = reference.identifier;
|
|
30670
30754
|
if (isAstDescendant(identifier, effectNode)) continue;
|
|
30671
30755
|
if (isIndependentWriterIdentifier(componentFunction, identifier, includeDeferredWriters)) return true;
|
|
30756
|
+
if (!isNodeReachableWithinFunction(identifier, context)) continue;
|
|
30757
|
+
const writerFunction = findEnclosingFunction$1(identifier);
|
|
30758
|
+
if (!writerFunction || writerFunction === componentFunction) continue;
|
|
30759
|
+
const writerVariable = getImmutableFunctionVariable(analysis, componentFunction, writerFunction);
|
|
30760
|
+
if (writerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, writerVariable, /* @__PURE__ */ new Set())) return true;
|
|
30672
30761
|
}
|
|
30673
30762
|
return false;
|
|
30674
30763
|
};
|
|
@@ -31558,7 +31647,7 @@ const areInMutuallyExclusiveBranches = (leftNode, rightNode) => {
|
|
|
31558
31647
|
}
|
|
31559
31648
|
return false;
|
|
31560
31649
|
};
|
|
31561
|
-
const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) => {
|
|
31650
|
+
const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFilename) => {
|
|
31562
31651
|
const frames = collectBoundedEffectExecutionFrames(analysis, effectNode, currentFilename);
|
|
31563
31652
|
if (frames.length === 0) return [];
|
|
31564
31653
|
const effectHasCleanup = hasCleanup(analysis, effectNode);
|
|
@@ -31588,7 +31677,7 @@ const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) =>
|
|
|
31588
31677
|
for (const returnedExpression of returnedExpressions) mergeEvidence(valueEvidence, collectValueEvidence(analysis, returnedExpression, updaterFrame, remainingValueCallFrames));
|
|
31589
31678
|
} else valueEvidence = collectValueEvidence(analysis, writtenValue, frame, remainingValueCallFrames);
|
|
31590
31679
|
const sourceReferences = [...valueEvidence.sourceReferences].filter((sourceReference) => getUseStateDecl(analysis, sourceReference) !== stateDeclarator);
|
|
31591
|
-
const hasIndependentWriter = hasUserInputSetterWriter(setterReference, effectNode, true);
|
|
31680
|
+
const hasIndependentWriter = hasUserInputSetterWriter(analysis, context, setterReference, effectNode, true);
|
|
31592
31681
|
const doesMatchStateInitializer = matchesStateInitializer(analysis, callExpression, stateDeclarator);
|
|
31593
31682
|
if (effectHasCleanup && (frame.isDeferred || valueEvidence.hasUnknownSource || valueEvidence.hasDeferredIntroducedValue || valueEvidence.readsExternalValue)) cleanupManagedStateDeclarators.add(stateDeclarator);
|
|
31594
31683
|
const isRenderKnownCopy = sourceReferences.length > 0 && !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue && !hasIndependentWriter;
|
|
@@ -31629,7 +31718,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
31629
31718
|
const dependencyReferences = getEffectDepsRefs(analysis, node);
|
|
31630
31719
|
if (!dependencyReferences) return;
|
|
31631
31720
|
if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
|
|
31632
|
-
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
31721
|
+
for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
|
|
31633
31722
|
if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
|
|
31634
31723
|
context.report({
|
|
31635
31724
|
node: fact.callExpression,
|
|
@@ -36203,7 +36292,7 @@ const noDerivedState = defineRule({
|
|
|
36203
36292
|
if (!isUseEffect(node)) return;
|
|
36204
36293
|
const analysis = getProgramAnalysis(node);
|
|
36205
36294
|
if (!analysis) return;
|
|
36206
|
-
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
36295
|
+
for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
|
|
36207
36296
|
if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
|
|
36208
36297
|
reportStateWrite(fact.callExpression, fact.stateDeclarator);
|
|
36209
36298
|
}
|
|
@@ -36223,7 +36312,7 @@ const noDerivedStateEffect = defineRule({
|
|
|
36223
36312
|
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
36224
36313
|
const analysis = getProgramAnalysis(node);
|
|
36225
36314
|
if (!analysis) return;
|
|
36226
|
-
if (!collectEffectStateWriteFacts(analysis, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
|
|
36315
|
+
if (!collectEffectStateWriteFacts(analysis, context, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
|
|
36227
36316
|
context.report({
|
|
36228
36317
|
node,
|
|
36229
36318
|
message: "You pay an extra render for state you can derive from other values."
|
|
@@ -36699,9 +36788,20 @@ const noDidMountSetState = defineRule({
|
|
|
36699
36788
|
}
|
|
36700
36789
|
});
|
|
36701
36790
|
//#endregion
|
|
36791
|
+
//#region src/plugin/utils/find-enclosing-class.ts
|
|
36792
|
+
const findEnclosingClass = (node) => {
|
|
36793
|
+
let ancestor = node.parent;
|
|
36794
|
+
while (ancestor) {
|
|
36795
|
+
if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
|
|
36796
|
+
ancestor = ancestor.parent ?? null;
|
|
36797
|
+
}
|
|
36798
|
+
return null;
|
|
36799
|
+
};
|
|
36800
|
+
//#endregion
|
|
36702
36801
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
36703
36802
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
36704
36803
|
const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
36804
|
+
const DIFFERENCE_OPERATORS = new Set(["!=", "!=="]);
|
|
36705
36805
|
const EQUALITY_OPERATORS = new Set([
|
|
36706
36806
|
"==",
|
|
36707
36807
|
"===",
|
|
@@ -36713,6 +36813,8 @@ const FUNCTION_NODE_TYPES = new Set([
|
|
|
36713
36813
|
"FunctionExpression",
|
|
36714
36814
|
"ArrowFunctionExpression"
|
|
36715
36815
|
]);
|
|
36816
|
+
const CLASS_NODE_TYPES = new Set(["ClassDeclaration", "ClassExpression"]);
|
|
36817
|
+
const callbackRefFieldNamesByClass = /* @__PURE__ */ new WeakMap();
|
|
36716
36818
|
const isLifecycleMethodFunction = (node) => {
|
|
36717
36819
|
if (!FUNCTION_NODE_TYPES.has(node.type)) return false;
|
|
36718
36820
|
const parent = node.parent;
|
|
@@ -36768,6 +36870,187 @@ const getStaticMemberName = (node) => {
|
|
|
36768
36870
|
if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
|
|
36769
36871
|
return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
|
|
36770
36872
|
};
|
|
36873
|
+
const getMemberIdentity = (property) => {
|
|
36874
|
+
const propertyName = getPropertyKeyName$2(property);
|
|
36875
|
+
if (propertyName !== void 0) return isNodeOfType(property, "PrivateIdentifier") ? `#${propertyName}` : propertyName;
|
|
36876
|
+
return isNodeOfType(property, "Literal") && typeof property.value === "string" ? property.value : null;
|
|
36877
|
+
};
|
|
36878
|
+
const collectPreviousSourcePaths = (pattern, domain, members, previousSourcePaths) => {
|
|
36879
|
+
if (!pattern) return;
|
|
36880
|
+
const unwrappedPattern = stripParenExpression(pattern);
|
|
36881
|
+
if (isNodeOfType(unwrappedPattern, "Identifier")) {
|
|
36882
|
+
previousSourcePaths.set(unwrappedPattern.name, {
|
|
36883
|
+
domain,
|
|
36884
|
+
members: [...members],
|
|
36885
|
+
source: "previous"
|
|
36886
|
+
});
|
|
36887
|
+
return;
|
|
36888
|
+
}
|
|
36889
|
+
if (isNodeOfType(unwrappedPattern, "AssignmentPattern")) {
|
|
36890
|
+
collectPreviousSourcePaths(unwrappedPattern.left, domain, members, previousSourcePaths);
|
|
36891
|
+
return;
|
|
36892
|
+
}
|
|
36893
|
+
if (!isNodeOfType(unwrappedPattern, "ObjectPattern")) return;
|
|
36894
|
+
for (const property of unwrappedPattern.properties) {
|
|
36895
|
+
if (!isNodeOfType(property, "Property")) continue;
|
|
36896
|
+
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
36897
|
+
if (!propertyName) continue;
|
|
36898
|
+
collectPreviousSourcePaths(property.value, domain, [...members, propertyName], previousSourcePaths);
|
|
36899
|
+
}
|
|
36900
|
+
};
|
|
36901
|
+
const getStateSourcePath = (node, previousSourcePaths) => {
|
|
36902
|
+
let currentNode = stripParenExpression(node);
|
|
36903
|
+
const members = [];
|
|
36904
|
+
while (isNodeOfType(currentNode, "MemberExpression")) {
|
|
36905
|
+
const memberName = getStaticMemberName(currentNode);
|
|
36906
|
+
if (!memberName) return null;
|
|
36907
|
+
members.unshift(memberName);
|
|
36908
|
+
currentNode = stripParenExpression(currentNode.object);
|
|
36909
|
+
}
|
|
36910
|
+
if (isNodeOfType(currentNode, "ThisExpression")) {
|
|
36911
|
+
const [domain, ...pathMembers] = members;
|
|
36912
|
+
if (domain !== "props" && domain !== "state") return null;
|
|
36913
|
+
return {
|
|
36914
|
+
domain,
|
|
36915
|
+
members: pathMembers,
|
|
36916
|
+
source: "current"
|
|
36917
|
+
};
|
|
36918
|
+
}
|
|
36919
|
+
if (!isNodeOfType(currentNode, "Identifier")) return null;
|
|
36920
|
+
const previousSourcePath = previousSourcePaths.get(currentNode.name);
|
|
36921
|
+
return previousSourcePath ? {
|
|
36922
|
+
...previousSourcePath,
|
|
36923
|
+
members: [...previousSourcePath.members, ...members]
|
|
36924
|
+
} : null;
|
|
36925
|
+
};
|
|
36926
|
+
const haveMatchingStateSourcePaths = (left, right) => left.domain === right.domain && left.members.length === right.members.length && left.members.every((member, index) => member === right.members[index]);
|
|
36927
|
+
const collectConjunctiveStateSourceComparisons = (test, previousSourcePaths, comparisons) => {
|
|
36928
|
+
const expression = stripParenExpression(test);
|
|
36929
|
+
if (isNodeOfType(expression, "LogicalExpression") && expression.operator === "&&") {
|
|
36930
|
+
collectConjunctiveStateSourceComparisons(expression.left, previousSourcePaths, comparisons);
|
|
36931
|
+
collectConjunctiveStateSourceComparisons(expression.right, previousSourcePaths, comparisons);
|
|
36932
|
+
return;
|
|
36933
|
+
}
|
|
36934
|
+
if (!isNodeOfType(expression, "BinaryExpression") || !EQUALITY_OPERATORS.has(expression.operator)) return;
|
|
36935
|
+
const leftPath = getStateSourcePath(expression.left, previousSourcePaths);
|
|
36936
|
+
const rightPath = getStateSourcePath(expression.right, previousSourcePaths);
|
|
36937
|
+
if (Boolean(leftPath) === Boolean(rightPath)) return;
|
|
36938
|
+
const path = leftPath ?? rightPath;
|
|
36939
|
+
if (!path) return;
|
|
36940
|
+
comparisons.push({
|
|
36941
|
+
comparedValue: leftPath ? expression.right : expression.left,
|
|
36942
|
+
isDifference: DIFFERENCE_OPERATORS.has(expression.operator),
|
|
36943
|
+
path
|
|
36944
|
+
});
|
|
36945
|
+
};
|
|
36946
|
+
const isHistoricalToCurrentTransitionGuard = (test, previousSourcePaths) => {
|
|
36947
|
+
const expression = stripParenExpression(test);
|
|
36948
|
+
if (isNodeOfType(expression, "LogicalExpression") && expression.operator === "||") return isHistoricalToCurrentTransitionGuard(expression.left, previousSourcePaths) && isHistoricalToCurrentTransitionGuard(expression.right, previousSourcePaths);
|
|
36949
|
+
const comparisons = [];
|
|
36950
|
+
collectConjunctiveStateSourceComparisons(expression, previousSourcePaths, comparisons);
|
|
36951
|
+
return comparisons.some((comparison, index) => comparisons.slice(index + 1).some((candidate) => comparison.path.source !== candidate.path.source && comparison.isDifference !== candidate.isDifference && haveMatchingStateSourcePaths(comparison.path, candidate.path) && areExpressionsStructurallyEqual(comparison.comparedValue, candidate.comparedValue)));
|
|
36952
|
+
};
|
|
36953
|
+
const getThisFieldName = (node) => {
|
|
36954
|
+
const unwrappedNode = stripParenExpression(node);
|
|
36955
|
+
if (!isNodeOfType(unwrappedNode, "MemberExpression") || unwrappedNode.computed === true || !isNodeOfType(stripParenExpression(unwrappedNode.object), "ThisExpression")) return null;
|
|
36956
|
+
return getMemberIdentity(unwrappedNode.property);
|
|
36957
|
+
};
|
|
36958
|
+
const isUndefinedIdentifier = (node) => {
|
|
36959
|
+
const unwrappedNode = stripParenExpression(node);
|
|
36960
|
+
return isNodeOfType(unwrappedNode, "Identifier") && unwrappedNode.name === "undefined";
|
|
36961
|
+
};
|
|
36962
|
+
const isDirectRefParameterValue = (node, parameterSymbolId, scopes) => {
|
|
36963
|
+
const unwrappedNode = stripParenExpression(node);
|
|
36964
|
+
if (isNodeOfType(unwrappedNode, "Identifier")) return scopes.symbolFor(unwrappedNode)?.id === parameterSymbolId;
|
|
36965
|
+
if (!isNodeOfType(unwrappedNode, "LogicalExpression") || unwrappedNode.operator !== "??") return false;
|
|
36966
|
+
const left = stripParenExpression(unwrappedNode.left);
|
|
36967
|
+
return isNodeOfType(left, "Identifier") && scopes.symbolFor(left)?.id === parameterSymbolId && isUndefinedIdentifier(unwrappedNode.right);
|
|
36968
|
+
};
|
|
36969
|
+
const getCallbackRefAssignedFields = (callback, scopes) => {
|
|
36970
|
+
const firstParameter = (callback.params ?? [])[0];
|
|
36971
|
+
if (!firstParameter) return /* @__PURE__ */ new Set();
|
|
36972
|
+
const parameterIdentifier = isNodeOfType(firstParameter, "AssignmentPattern") ? firstParameter.left : firstParameter;
|
|
36973
|
+
if (!isNodeOfType(parameterIdentifier, "Identifier")) return /* @__PURE__ */ new Set();
|
|
36974
|
+
const parameterSymbolId = scopes.symbolFor(parameterIdentifier)?.id;
|
|
36975
|
+
if (parameterSymbolId === void 0) return /* @__PURE__ */ new Set();
|
|
36976
|
+
const body = callback.body;
|
|
36977
|
+
if (!body) return /* @__PURE__ */ new Set();
|
|
36978
|
+
const assignedFieldNames = /* @__PURE__ */ new Set();
|
|
36979
|
+
walkAst(body, (node) => {
|
|
36980
|
+
if (node !== body && (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node) || CLASS_NODE_TYPES.has(node.type))) return false;
|
|
36981
|
+
const assignmentTarget = isNodeOfType(node, "AssignmentExpression") && node.left || isNodeOfType(node, "UpdateExpression") && node.argument || isNodeOfType(node, "UnaryExpression") && node.operator === "delete" && node.argument || null;
|
|
36982
|
+
if (!assignmentTarget) return;
|
|
36983
|
+
const fieldName = getThisFieldName(assignmentTarget);
|
|
36984
|
+
if (!fieldName) return;
|
|
36985
|
+
if (isNodeOfType(node, "AssignmentExpression") && node.operator === "=" && isDirectRefParameterValue(node.right, parameterSymbolId, scopes)) {
|
|
36986
|
+
assignedFieldNames.add(fieldName);
|
|
36987
|
+
return;
|
|
36988
|
+
}
|
|
36989
|
+
assignedFieldNames.delete(fieldName);
|
|
36990
|
+
});
|
|
36991
|
+
return assignedFieldNames;
|
|
36992
|
+
};
|
|
36993
|
+
const getClassMemberCallback = (classNode, memberName) => {
|
|
36994
|
+
const classBody = classNode.body?.body ?? [];
|
|
36995
|
+
for (const member of classBody) {
|
|
36996
|
+
if (!isNodeOfType(member, "MethodDefinition") && !isNodeOfType(member, "PropertyDefinition")) continue;
|
|
36997
|
+
if (member.static === true) continue;
|
|
36998
|
+
const key = member.key;
|
|
36999
|
+
if (getMemberIdentity(key) !== memberName) continue;
|
|
37000
|
+
const value = member.value;
|
|
37001
|
+
return value && FUNCTION_NODE_TYPES.has(value.type) ? value : null;
|
|
37002
|
+
}
|
|
37003
|
+
return null;
|
|
37004
|
+
};
|
|
37005
|
+
const collectCallbackRefFieldsFromExpression = (expression, classNode, fieldNames, scopes) => {
|
|
37006
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
37007
|
+
if (FUNCTION_NODE_TYPES.has(unwrappedExpression.type)) {
|
|
37008
|
+
for (const fieldName of getCallbackRefAssignedFields(unwrappedExpression, scopes)) fieldNames.add(fieldName);
|
|
37009
|
+
return;
|
|
37010
|
+
}
|
|
37011
|
+
const handlerName = getThisFieldName(unwrappedExpression);
|
|
37012
|
+
if (handlerName) {
|
|
37013
|
+
const callback = getClassMemberCallback(classNode, handlerName);
|
|
37014
|
+
if (callback) for (const fieldName of getCallbackRefAssignedFields(callback, scopes)) fieldNames.add(fieldName);
|
|
37015
|
+
return;
|
|
37016
|
+
}
|
|
37017
|
+
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
|
|
37018
|
+
collectCallbackRefFieldsFromExpression(unwrappedExpression.consequent, classNode, fieldNames, scopes);
|
|
37019
|
+
collectCallbackRefFieldsFromExpression(unwrappedExpression.alternate, classNode, fieldNames, scopes);
|
|
37020
|
+
return;
|
|
37021
|
+
}
|
|
37022
|
+
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
|
|
37023
|
+
if (unwrappedExpression.operator !== "&&") collectCallbackRefFieldsFromExpression(unwrappedExpression.left, classNode, fieldNames, scopes);
|
|
37024
|
+
collectCallbackRefFieldsFromExpression(unwrappedExpression.right, classNode, fieldNames, scopes);
|
|
37025
|
+
}
|
|
37026
|
+
};
|
|
37027
|
+
const getCallbackRefFieldNames = (classNode, scopes) => {
|
|
37028
|
+
if (!classNode) return /* @__PURE__ */ new Set();
|
|
37029
|
+
const cachedFieldNames = callbackRefFieldNamesByClass.get(classNode);
|
|
37030
|
+
if (cachedFieldNames) return cachedFieldNames;
|
|
37031
|
+
const fieldNames = /* @__PURE__ */ new Set();
|
|
37032
|
+
const classBody = classNode.body;
|
|
37033
|
+
if (classBody) walkAst(classBody, (node) => {
|
|
37034
|
+
if (node !== classBody && CLASS_NODE_TYPES.has(node.type)) return false;
|
|
37035
|
+
if (!isNodeOfType(node, "JSXAttribute") || !isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "ref" || !node.value || !isNodeOfType(node.value, "JSXExpressionContainer") || !node.value.expression) return;
|
|
37036
|
+
collectCallbackRefFieldsFromExpression(node.value.expression, classNode, fieldNames, scopes);
|
|
37037
|
+
});
|
|
37038
|
+
callbackRefFieldNamesByClass.set(classNode, fieldNames);
|
|
37039
|
+
return fieldNames;
|
|
37040
|
+
};
|
|
37041
|
+
const collectLifecycleWrittenFieldNames = (lifecycleFunction) => {
|
|
37042
|
+
const fieldNames = /* @__PURE__ */ new Set();
|
|
37043
|
+
const body = lifecycleFunction.body;
|
|
37044
|
+
if (!body) return fieldNames;
|
|
37045
|
+
walkAst(body, (node) => {
|
|
37046
|
+
if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
|
|
37047
|
+
const target = isNodeOfType(node, "AssignmentExpression") && node.left || isNodeOfType(node, "UpdateExpression") && node.argument || null;
|
|
37048
|
+
if (!target) return;
|
|
37049
|
+
const fieldName = getThisFieldName(target);
|
|
37050
|
+
if (fieldName) fieldNames.add(fieldName);
|
|
37051
|
+
});
|
|
37052
|
+
return fieldNames;
|
|
37053
|
+
};
|
|
36771
37054
|
const getThisStateFieldName = (node) => {
|
|
36772
37055
|
const unwrappedNode = stripParenExpression(node);
|
|
36773
37056
|
if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
|
|
@@ -36785,15 +37068,17 @@ const collectLocalInitializers = (lifecycleFunction) => {
|
|
|
36785
37068
|
});
|
|
36786
37069
|
return initializers;
|
|
36787
37070
|
};
|
|
36788
|
-
const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
|
|
37071
|
+
const derivesFromPostMountValue = (node, localInitializers, callbackRefFieldNames, visitedNames = /* @__PURE__ */ new Set()) => {
|
|
36789
37072
|
if (readsPostMountValue(node)) return true;
|
|
37073
|
+
const fieldName = getThisFieldName(node);
|
|
37074
|
+
if (fieldName && callbackRefFieldNames.has(fieldName)) return true;
|
|
36790
37075
|
const referencedNames = /* @__PURE__ */ new Set();
|
|
36791
37076
|
collectReferenceIdentifierNames(node, referencedNames);
|
|
36792
37077
|
for (const referencedName of referencedNames) {
|
|
36793
37078
|
if (visitedNames.has(referencedName)) continue;
|
|
36794
37079
|
const initializer = localInitializers.get(referencedName);
|
|
36795
37080
|
if (!initializer) continue;
|
|
36796
|
-
if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
|
|
37081
|
+
if (derivesFromPostMountValue(initializer, localInitializers, callbackRefFieldNames, new Set([...visitedNames, referencedName]))) return true;
|
|
36797
37082
|
}
|
|
36798
37083
|
return false;
|
|
36799
37084
|
};
|
|
@@ -36807,50 +37092,84 @@ const getSetStateFieldValue = (setStateCall, fieldName) => {
|
|
|
36807
37092
|
}
|
|
36808
37093
|
return null;
|
|
36809
37094
|
};
|
|
36810
|
-
const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
|
|
36811
|
-
|
|
36812
|
-
|
|
36813
|
-
if (
|
|
36814
|
-
|
|
36815
|
-
const
|
|
36816
|
-
|
|
36817
|
-
|
|
36818
|
-
|
|
36819
|
-
|
|
36820
|
-
|
|
36821
|
-
|
|
36822
|
-
|
|
36823
|
-
|
|
36824
|
-
|
|
36825
|
-
|
|
36826
|
-
return
|
|
36827
|
-
};
|
|
36828
|
-
const
|
|
36829
|
-
|
|
36830
|
-
|
|
36831
|
-
|
|
36832
|
-
|
|
36833
|
-
|
|
36834
|
-
|
|
36835
|
-
|
|
36836
|
-
|
|
36837
|
-
|
|
36838
|
-
|
|
36839
|
-
|
|
36840
|
-
|
|
37095
|
+
const isConvergentPostMountGuard = (test, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch) => {
|
|
37096
|
+
const expression = stripParenExpression(test);
|
|
37097
|
+
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
37098
|
+
if (expression.operator !== "&&" && expression.operator !== "||") return false;
|
|
37099
|
+
const leftIsConvergent = isConvergentPostMountGuard(expression.left, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch);
|
|
37100
|
+
const rightIsConvergent = isConvergentPostMountGuard(expression.right, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch);
|
|
37101
|
+
return isTruthfulBranch && expression.operator === "||" || !isTruthfulBranch && expression.operator === "&&" ? leftIsConvergent && rightIsConvergent : leftIsConvergent || rightIsConvergent;
|
|
37102
|
+
}
|
|
37103
|
+
if (!isNodeOfType(expression, "BinaryExpression") || !(isTruthfulBranch ? DIFFERENCE_OPERATORS.has(expression.operator) : EQUALITY_OPERATORS.has(expression.operator) && !DIFFERENCE_OPERATORS.has(expression.operator))) return false;
|
|
37104
|
+
const leftFieldName = getThisStateFieldName(expression.left);
|
|
37105
|
+
const rightFieldName = getThisStateFieldName(expression.right);
|
|
37106
|
+
const fieldName = leftFieldName ?? rightFieldName;
|
|
37107
|
+
const comparedValue = leftFieldName ? expression.right : expression.left;
|
|
37108
|
+
if (!fieldName) return false;
|
|
37109
|
+
const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
|
|
37110
|
+
if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return false;
|
|
37111
|
+
return isUndefinedIdentifier(comparedValue) || derivesFromPostMountValue(comparedValue, localInitializers, callbackRefFieldNames);
|
|
37112
|
+
};
|
|
37113
|
+
const containsPositiveStateFieldTest = (test, fieldName) => {
|
|
37114
|
+
const unwrappedTest = stripParenExpression(test);
|
|
37115
|
+
if (getThisStateFieldName(unwrappedTest) === fieldName) return true;
|
|
37116
|
+
return isNodeOfType(unwrappedTest, "LogicalExpression") && unwrappedTest.operator === "&&" && (containsPositiveStateFieldTest(unwrappedTest.left, fieldName) || containsPositiveStateFieldTest(unwrappedTest.right, fieldName));
|
|
37117
|
+
};
|
|
37118
|
+
const isConvergentUndefinedClearGuard = (test, setStateCall) => {
|
|
37119
|
+
if (!isNodeOfType(setStateCall, "CallExpression")) return false;
|
|
37120
|
+
const argument = setStateCall.arguments?.[0];
|
|
37121
|
+
if (!argument || !isNodeOfType(argument, "ObjectExpression")) return false;
|
|
37122
|
+
for (const property of argument.properties ?? []) {
|
|
37123
|
+
if (!isNodeOfType(property, "Property") || property.computed === true || !isUndefinedIdentifier(property.value)) continue;
|
|
37124
|
+
const fieldName = isNodeOfType(property.key, "Identifier") && property.key.name || isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" && property.key.value || null;
|
|
37125
|
+
if (fieldName && containsPositiveStateFieldTest(test, fieldName)) return true;
|
|
37126
|
+
}
|
|
37127
|
+
return false;
|
|
37128
|
+
};
|
|
37129
|
+
const isDiffGuardTest = (test, paramNames, derivedNames, isTruthfulBranch) => {
|
|
37130
|
+
const expression = stripParenExpression(test);
|
|
37131
|
+
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
37132
|
+
if (expression.operator !== "&&" && expression.operator !== "||") return false;
|
|
37133
|
+
const leftIsDiffGuard = isDiffGuardTest(expression.left, paramNames, derivedNames, isTruthfulBranch);
|
|
37134
|
+
const rightIsDiffGuard = isDiffGuardTest(expression.right, paramNames, derivedNames, isTruthfulBranch);
|
|
37135
|
+
return isTruthfulBranch && expression.operator === "||" || !isTruthfulBranch && expression.operator === "&&" ? leftIsDiffGuard && rightIsDiffGuard : leftIsDiffGuard || rightIsDiffGuard;
|
|
37136
|
+
}
|
|
37137
|
+
if (!isNodeOfType(expression, "BinaryExpression") || !(isTruthfulBranch ? DIFFERENCE_OPERATORS.has(expression.operator) : EQUALITY_OPERATORS.has(expression.operator) && !DIFFERENCE_OPERATORS.has(expression.operator))) return false;
|
|
37138
|
+
return isStatefulOperand(expression.left, paramNames, derivedNames) && isStatefulOperand(expression.right, paramNames, derivedNames) && (referencesAnyName(expression.left, paramNames) || referencesAnyName(expression.right, paramNames) || referencesAnyName(expression.left, derivedNames) || referencesAnyName(expression.right, derivedNames));
|
|
36841
37139
|
};
|
|
36842
|
-
const isInsideDiffGuard = (setStateCall) => {
|
|
37140
|
+
const isInsideDiffGuard = (setStateCall, scopes) => {
|
|
36843
37141
|
const lifecycleFunction = findEnclosingLifecycleFunction(setStateCall);
|
|
36844
37142
|
if (!lifecycleFunction) return false;
|
|
36845
37143
|
const paramNames = /* @__PURE__ */ new Set();
|
|
36846
|
-
|
|
37144
|
+
const parameters = lifecycleFunction.params ?? [];
|
|
37145
|
+
for (const param of parameters) collectPatternNames(param, paramNames);
|
|
37146
|
+
const previousSourcePaths = /* @__PURE__ */ new Map();
|
|
37147
|
+
const [previousPropsParameter, previousStateParameter] = parameters;
|
|
37148
|
+
collectPreviousSourcePaths(previousPropsParameter, "props", [], previousSourcePaths);
|
|
37149
|
+
collectPreviousSourcePaths(previousStateParameter, "state", [], previousSourcePaths);
|
|
36847
37150
|
const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
|
|
36848
37151
|
const localInitializers = collectLocalInitializers(lifecycleFunction);
|
|
37152
|
+
const lifecycleWrittenFieldNames = collectLifecycleWrittenFieldNames(lifecycleFunction);
|
|
37153
|
+
const callbackRefFieldNames = new Set([...getCallbackRefFieldNames(findEnclosingClass(lifecycleFunction), scopes)].filter((fieldName) => !lifecycleWrittenFieldNames.has(fieldName)));
|
|
36849
37154
|
let child = setStateCall;
|
|
36850
37155
|
let ancestor = setStateCall.parent;
|
|
36851
37156
|
while (ancestor && ancestor !== lifecycleFunction) {
|
|
36852
|
-
|
|
36853
|
-
|
|
37157
|
+
let guardTest = null;
|
|
37158
|
+
let isTruthfulBranch = true;
|
|
37159
|
+
if (isNodeOfType(ancestor, "IfStatement")) {
|
|
37160
|
+
if (child === ancestor.consequent) guardTest = ancestor.test;
|
|
37161
|
+
else if (child === ancestor.alternate) {
|
|
37162
|
+
guardTest = ancestor.test;
|
|
37163
|
+
isTruthfulBranch = false;
|
|
37164
|
+
}
|
|
37165
|
+
} else if (isNodeOfType(ancestor, "ConditionalExpression")) {
|
|
37166
|
+
if (child === ancestor.consequent) guardTest = ancestor.test;
|
|
37167
|
+
else if (child === ancestor.alternate) {
|
|
37168
|
+
guardTest = ancestor.test;
|
|
37169
|
+
isTruthfulBranch = false;
|
|
37170
|
+
}
|
|
37171
|
+
} else if (isNodeOfType(ancestor, "LogicalExpression") && ancestor.operator === "&&" && child === ancestor.right) guardTest = ancestor.left;
|
|
37172
|
+
if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames, isTruthfulBranch) || isTruthfulBranch && isHistoricalToCurrentTransitionGuard(guardTest, previousSourcePaths) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch) || isTruthfulBranch && isConvergentUndefinedClearGuard(guardTest, setStateCall))) return true;
|
|
36854
37173
|
child = ancestor;
|
|
36855
37174
|
ancestor = ancestor.parent ?? null;
|
|
36856
37175
|
}
|
|
@@ -36872,7 +37191,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
36872
37191
|
if (!isNodeOfType(stripParenExpression(node.callee.object), "ThisExpression")) return;
|
|
36873
37192
|
if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
|
|
36874
37193
|
if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
36875
|
-
if (isInsideDiffGuard(node)) return;
|
|
37194
|
+
if (isInsideDiffGuard(node, context.scopes)) return;
|
|
36876
37195
|
context.report({
|
|
36877
37196
|
node: node.callee,
|
|
36878
37197
|
message: MESSAGE$27
|
|
@@ -41163,7 +41482,7 @@ const noInitializeState = defineRule({
|
|
|
41163
41482
|
if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
|
|
41164
41483
|
const analysis = getProgramAnalysis(node);
|
|
41165
41484
|
if (!analysis) return;
|
|
41166
|
-
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
41485
|
+
for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
|
|
41167
41486
|
if (!fact.isRenderKnownCopy || fact.matchesStateInitializer || fact.resetsSourceState) continue;
|
|
41168
41487
|
const stateName = getStateName(fact.stateDeclarator);
|
|
41169
41488
|
context.report({
|
|
@@ -44570,6 +44889,114 @@ const DATA_SINK_METHOD_NAMES = new Set([
|
|
|
44570
44889
|
"deserialize"
|
|
44571
44890
|
]);
|
|
44572
44891
|
//#endregion
|
|
44892
|
+
//#region src/plugin/utils/get-transparent-react-callback-wrapper-argument.ts
|
|
44893
|
+
const getTransparentReactCallbackWrapperArgument = (initializer, resultSymbol, scopes) => {
|
|
44894
|
+
const callExpression = stripParenExpression(initializer);
|
|
44895
|
+
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
44896
|
+
const callbackArgument = callExpression.arguments[0];
|
|
44897
|
+
if (!callbackArgument) return null;
|
|
44898
|
+
if (resultSymbol && symbolHasReactUseEffectEventOrigin(resultSymbol, scopes)) return callbackArgument;
|
|
44899
|
+
return isReactApiCall(callExpression, "useCallback", scopes, {
|
|
44900
|
+
allowGlobalReactNamespace: true,
|
|
44901
|
+
allowUnboundBareCalls: true
|
|
44902
|
+
}) ? callbackArgument : null;
|
|
44903
|
+
};
|
|
44904
|
+
//#endregion
|
|
44905
|
+
//#region src/plugin/rules/state-and-effects/utils/resolve-parent-callback-provenance.ts
|
|
44906
|
+
const getDeclarationKind$1 = (declarator) => {
|
|
44907
|
+
const declaration = declarator.parent;
|
|
44908
|
+
return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
|
|
44909
|
+
};
|
|
44910
|
+
const hasMutableBindingWrite$2 = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
44911
|
+
const mergeRequiredBranches = (leftNames, rightNames) => {
|
|
44912
|
+
if (!leftNames || !rightNames) return null;
|
|
44913
|
+
return new Set([...leftNames, ...rightNames]);
|
|
44914
|
+
};
|
|
44915
|
+
const getPropReferenceName = (analysis, identifier) => {
|
|
44916
|
+
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
44917
|
+
const reference = getRef(analysis, identifier);
|
|
44918
|
+
if (!reference || !isProp(analysis, reference) || isWholePropsObjectReference(analysis, reference)) return null;
|
|
44919
|
+
const bindingIdentifier = (reference.resolved?.defs.find((definition) => definition.type === "Parameter"))?.name;
|
|
44920
|
+
return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? identifier.name;
|
|
44921
|
+
};
|
|
44922
|
+
const getSingleConstDeclarator = (reference) => {
|
|
44923
|
+
if (!reference.resolved || hasMutableBindingWrite$2(reference)) return null;
|
|
44924
|
+
const declarators = reference.resolved.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
44925
|
+
if (declarators.length !== 1) return null;
|
|
44926
|
+
const declarator = declarators[0];
|
|
44927
|
+
if (!declarator || getDeclarationKind$1(declarator) !== "const") return null;
|
|
44928
|
+
return declarator;
|
|
44929
|
+
};
|
|
44930
|
+
const resolveParentCallbackPropNames = (analysis, expression, scopes, visitedReferences, allowFunctionForwarder = false) => {
|
|
44931
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
44932
|
+
if (isFunctionLike$1(unwrappedExpression)) {
|
|
44933
|
+
if (!allowFunctionForwarder || Boolean(unwrappedExpression.async)) return null;
|
|
44934
|
+
const callbackNames = /* @__PURE__ */ new Set();
|
|
44935
|
+
walkInsideStatementBlocks(unwrappedExpression.body, (child) => {
|
|
44936
|
+
if (!isNodeOfType(child, "CallExpression")) return;
|
|
44937
|
+
const resolvedNames = resolveParentCallbackPropNames(analysis, child.callee, scopes, new Set(visitedReferences), false);
|
|
44938
|
+
if (!resolvedNames) return;
|
|
44939
|
+
for (const resolvedName of resolvedNames) callbackNames.add(resolvedName);
|
|
44940
|
+
});
|
|
44941
|
+
return callbackNames.size > 0 ? callbackNames : null;
|
|
44942
|
+
}
|
|
44943
|
+
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return mergeRequiredBranches(resolveParentCallbackPropNames(analysis, unwrappedExpression.consequent, scopes, new Set(visitedReferences), false), resolveParentCallbackPropNames(analysis, unwrappedExpression.alternate, scopes, new Set(visitedReferences), false));
|
|
44944
|
+
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) return mergeRequiredBranches(resolveParentCallbackPropNames(analysis, unwrappedExpression.left, scopes, new Set(visitedReferences), false), resolveParentCallbackPropNames(analysis, unwrappedExpression.right, scopes, new Set(visitedReferences)));
|
|
44945
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
44946
|
+
const propName = getPropReferenceName(analysis, unwrappedExpression);
|
|
44947
|
+
if (propName) return new Set([propName]);
|
|
44948
|
+
const reference = getRef(analysis, unwrappedExpression);
|
|
44949
|
+
if (!reference?.resolved || visitedReferences.has(reference.resolved)) return null;
|
|
44950
|
+
const declarator = getSingleConstDeclarator(reference);
|
|
44951
|
+
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return null;
|
|
44952
|
+
visitedReferences.add(reference.resolved);
|
|
44953
|
+
const wrappedArgument = getTransparentReactCallbackWrapperArgument(declarator.init, scopes.symbolFor(unwrappedExpression), scopes);
|
|
44954
|
+
const allowsFunctionForwarder = Boolean(wrappedArgument && !isReactApiCall(declarator.init, "useCallback", scopes, {
|
|
44955
|
+
allowGlobalReactNamespace: true,
|
|
44956
|
+
allowUnboundBareCalls: true
|
|
44957
|
+
}));
|
|
44958
|
+
return resolveParentCallbackPropNames(analysis, wrappedArgument ?? declarator.init, scopes, visitedReferences, allowsFunctionForwarder);
|
|
44959
|
+
}
|
|
44960
|
+
if (!isNodeOfType(unwrappedExpression, "MemberExpression")) return null;
|
|
44961
|
+
const propertyName = getStaticMemberPropertyName(unwrappedExpression);
|
|
44962
|
+
if (!propertyName) return null;
|
|
44963
|
+
const receiver = stripParenExpression(unwrappedExpression.object);
|
|
44964
|
+
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
44965
|
+
const receiverReference = getRef(analysis, receiver);
|
|
44966
|
+
if (!receiverReference?.resolved || visitedReferences.has(receiverReference.resolved)) return null;
|
|
44967
|
+
if (isWholePropsObjectReference(analysis, receiverReference)) return new Set([propertyName]);
|
|
44968
|
+
const declarator = getSingleConstDeclarator(receiverReference);
|
|
44969
|
+
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return null;
|
|
44970
|
+
visitedReferences.add(receiverReference.resolved);
|
|
44971
|
+
const initializer = stripParenExpression(declarator.init);
|
|
44972
|
+
if (propertyName === "current" && isNodeOfType(initializer, "CallExpression")) {
|
|
44973
|
+
if (!isReactApiCall(initializer, "useRef", scopes, {
|
|
44974
|
+
allowGlobalReactNamespace: true,
|
|
44975
|
+
allowUnboundBareCalls: true
|
|
44976
|
+
})) return null;
|
|
44977
|
+
const callbackArgument = initializer.arguments[0];
|
|
44978
|
+
if (!callbackArgument) return null;
|
|
44979
|
+
let callbackNames = resolveParentCallbackPropNames(analysis, callbackArgument, scopes, new Set(visitedReferences), false);
|
|
44980
|
+
if (!callbackNames) return null;
|
|
44981
|
+
for (const candidateReference of receiverReference.resolved.references) {
|
|
44982
|
+
const candidateIdentifier = candidateReference.identifier;
|
|
44983
|
+
const candidateMember = candidateIdentifier.parent;
|
|
44984
|
+
if (!candidateMember || !isNodeOfType(candidateMember, "MemberExpression") || candidateMember.object !== candidateIdentifier || getStaticMemberPropertyName(candidateMember) !== "current") continue;
|
|
44985
|
+
const assignment = candidateMember.parent;
|
|
44986
|
+
if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.left !== candidateMember) continue;
|
|
44987
|
+
if (assignment.operator !== "=") return null;
|
|
44988
|
+
callbackNames = mergeRequiredBranches(callbackNames, resolveParentCallbackPropNames(analysis, assignment.right, scopes, new Set(visitedReferences), false));
|
|
44989
|
+
if (!callbackNames) return null;
|
|
44990
|
+
}
|
|
44991
|
+
return callbackNames;
|
|
44992
|
+
}
|
|
44993
|
+
if (!isNodeOfType(initializer, "ObjectExpression")) return null;
|
|
44994
|
+
const property = initializer.properties.find((candidateProperty) => isNodeOfType(candidateProperty, "Property") && getStaticPropertyKeyName(candidateProperty, { allowComputedString: true }) === propertyName);
|
|
44995
|
+
if (!property || !isNodeOfType(property, "Property")) return null;
|
|
44996
|
+
return resolveParentCallbackPropNames(analysis, property.value, scopes, visitedReferences, false);
|
|
44997
|
+
};
|
|
44998
|
+
const getParentCallbackPropNames = ({ analysis, expression, scopes }) => resolveParentCallbackPropNames(analysis, expression, scopes, /* @__PURE__ */ new Set(), false);
|
|
44999
|
+
//#endregion
|
|
44573
45000
|
//#region src/plugin/rules/state-and-effects/no-pass-data-to-parent.ts
|
|
44574
45001
|
const isUseStateIdentifier = (identifier) => {
|
|
44575
45002
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
@@ -44598,14 +45025,18 @@ const FUNCTION_WRAPPER_HOOK_NAMES$1 = new Set([
|
|
|
44598
45025
|
"useStableCallback",
|
|
44599
45026
|
"useCallbackRef"
|
|
44600
45027
|
]);
|
|
44601
|
-
const getWrapperHookWrappedFunction = (initializer) => {
|
|
45028
|
+
const getWrapperHookWrappedFunction = (initializer, resultSymbol, scopes) => {
|
|
44602
45029
|
if (!isNodeOfType(initializer, "CallExpression")) return null;
|
|
45030
|
+
const transparentReactArgument = getTransparentReactCallbackWrapperArgument(initializer, resultSymbol, scopes);
|
|
45031
|
+
if (transparentReactArgument) return transparentReactArgument;
|
|
44603
45032
|
const callee = initializer.callee;
|
|
44604
45033
|
const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") ? callee.property.name : null;
|
|
44605
45034
|
if (!calleeName || !FUNCTION_WRAPPER_HOOK_NAMES$1.has(calleeName)) return null;
|
|
44606
45035
|
const wrapped = initializer.arguments?.[0];
|
|
44607
|
-
if (!wrapped
|
|
44608
|
-
return
|
|
45036
|
+
if (!wrapped) return null;
|
|
45037
|
+
if (calleeName === "useEffectEvent") return null;
|
|
45038
|
+
if (isFunctionLike$1(wrapped)) return wrapped;
|
|
45039
|
+
return null;
|
|
44609
45040
|
};
|
|
44610
45041
|
const HANDLER_NAMED_PROP_PATTERN = /^(on|handle)[A-Z]/;
|
|
44611
45042
|
const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstreamRefs(analysis, wrappedFunction).some((innerRef) => {
|
|
@@ -44615,16 +45046,29 @@ const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstre
|
|
|
44615
45046
|
const innerParent = innerIdentifier.parent;
|
|
44616
45047
|
return Boolean(innerParent && isNodeOfType(innerParent, "CallExpression") && innerParent.callee === innerIdentifier);
|
|
44617
45048
|
});
|
|
44618
|
-
const isDirectParentCallbackRef = (analysis, ref) => {
|
|
45049
|
+
const isDirectParentCallbackRef = (analysis, ref, scopes) => {
|
|
44619
45050
|
if (isProp(analysis, ref)) return true;
|
|
45051
|
+
if (hasMutableBindingWrite$1(ref)) {
|
|
45052
|
+
if (!(ref.resolved?.references.filter((candidateReference) => candidateReference.isWrite() && !candidateReference.init) ?? []).every((candidateReference) => {
|
|
45053
|
+
const candidateIdentifier = candidateReference.identifier;
|
|
45054
|
+
const assignment = candidateIdentifier.parent;
|
|
45055
|
+
if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== candidateIdentifier) return false;
|
|
45056
|
+
const assignedReferences = getDownstreamRefs(analysis, assignment.right);
|
|
45057
|
+
return assignedReferences.length > 0 && assignedReferences.every((assignedReference) => isProp(analysis, assignedReference));
|
|
45058
|
+
})) return false;
|
|
45059
|
+
}
|
|
44620
45060
|
return Boolean(ref.resolved?.defs.some((def) => {
|
|
44621
45061
|
const node = def.node;
|
|
44622
45062
|
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
44623
45063
|
const initializer = unwrapChainExpression(node.init);
|
|
44624
|
-
const wrappedFunction = getWrapperHookWrappedFunction(initializer);
|
|
45064
|
+
const wrappedFunction = getWrapperHookWrappedFunction(initializer, isNodeOfType(node.id, "Identifier") ? scopes.symbolFor(node.id) ?? null : null, scopes);
|
|
44625
45065
|
if (wrappedFunction) {
|
|
44626
45066
|
if (wrappedFunction.async) return false;
|
|
44627
|
-
return wrappedFunctionNotifiesParent(analysis, wrappedFunction);
|
|
45067
|
+
if (isFunctionLike$1(wrappedFunction)) return wrappedFunctionNotifiesParent(analysis, wrappedFunction);
|
|
45068
|
+
const directName = getParentCallbackPropName(analysis, wrappedFunction);
|
|
45069
|
+
const downstreamReferences = getDownstreamRefs(analysis, wrappedFunction);
|
|
45070
|
+
if (directName !== null) return true;
|
|
45071
|
+
return downstreamReferences.some((wrappedReference) => !hasMutableBindingWrite$1(wrappedReference) && getUpstreamRefs(analysis, wrappedReference).some((upstreamReference) => isProp(analysis, upstreamReference)));
|
|
44628
45072
|
}
|
|
44629
45073
|
if (!isNodeOfType(initializer, "Identifier") && !isNodeOfType(initializer, "MemberExpression")) return false;
|
|
44630
45074
|
return getDownstreamRefs(analysis, initializer).some((initializerRef) => getUpstreamRefs(analysis, initializerRef).some((upstreamRef) => isProp(analysis, upstreamRef)));
|
|
@@ -44634,7 +45078,7 @@ const getDeclarationKind = (declarator) => {
|
|
|
44634
45078
|
const declaration = declarator.parent;
|
|
44635
45079
|
return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
|
|
44636
45080
|
};
|
|
44637
|
-
const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
45081
|
+
const hasMutableBindingWrite$1 = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
44638
45082
|
const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
|
|
44639
45083
|
const unwrappedExpression = stripParenExpression(expression);
|
|
44640
45084
|
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
@@ -44646,7 +45090,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
44646
45090
|
const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
|
|
44647
45091
|
return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
|
|
44648
45092
|
}
|
|
44649
|
-
if (hasMutableBindingWrite(callbackReference)) return null;
|
|
45093
|
+
if (hasMutableBindingWrite$1(callbackReference)) return null;
|
|
44650
45094
|
const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
44651
45095
|
if (definitions.length !== 1) return null;
|
|
44652
45096
|
const declarator = definitions[0];
|
|
@@ -44712,7 +45156,7 @@ const getRefAliasDeclarator = (identifier) => {
|
|
|
44712
45156
|
const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
|
|
44713
45157
|
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
44714
45158
|
const receiverReference = getRef(analysis, receiver);
|
|
44715
|
-
if (!receiverReference?.resolved || hasMutableBindingWrite(receiverReference)) return null;
|
|
45159
|
+
if (!receiverReference?.resolved || hasMutableBindingWrite$1(receiverReference)) return null;
|
|
44716
45160
|
const variables = /* @__PURE__ */ new Set();
|
|
44717
45161
|
let currentVariable = receiverReference.resolved;
|
|
44718
45162
|
let refCall = null;
|
|
@@ -44728,7 +45172,7 @@ const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
|
|
|
44728
45172
|
}
|
|
44729
45173
|
if (getDeclarationKind(declarator) !== "const" || !isNodeOfType(stripParenExpression(declarator.init), "Identifier")) return null;
|
|
44730
45174
|
const upstreamReference = getRef(analysis, stripParenExpression(declarator.init));
|
|
44731
|
-
if (!upstreamReference?.resolved || hasMutableBindingWrite(upstreamReference)) return null;
|
|
45175
|
+
if (!upstreamReference?.resolved || hasMutableBindingWrite$1(upstreamReference)) return null;
|
|
44732
45176
|
currentVariable = upstreamReference.resolved;
|
|
44733
45177
|
}
|
|
44734
45178
|
if (!refCall) return null;
|
|
@@ -44803,7 +45247,7 @@ const isParentPropsContextMerge = (analysis, expression) => {
|
|
|
44803
45247
|
while (isNodeOfType(currentExpression, "Identifier")) {
|
|
44804
45248
|
const currentReference = getRef(analysis, currentExpression);
|
|
44805
45249
|
const currentVariable = currentReference?.resolved;
|
|
44806
|
-
if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return false;
|
|
45250
|
+
if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite$1(currentReference)) return false;
|
|
44807
45251
|
visitedVariables.add(currentVariable);
|
|
44808
45252
|
const definitions = currentVariable.defs.filter((definition) => isNodeOfType(definition.node, "VariableDeclarator"));
|
|
44809
45253
|
if (definitions.length !== 1) return false;
|
|
@@ -44817,11 +45261,11 @@ const isParentPropsContextMerge = (analysis, expression) => {
|
|
|
44817
45261
|
const propsExpression = stripParenExpression(propsSpread.argument);
|
|
44818
45262
|
if (!isNodeOfType(propsExpression, "Identifier")) return false;
|
|
44819
45263
|
const propsReference = getRef(analysis, propsExpression);
|
|
44820
|
-
if (!propsReference?.resolved || !isWholePropsObjectReference(analysis, propsReference) || hasMutableBindingWrite(propsReference) || propsReference.resolved.references.some((candidateReference) => candidateReference !== propsReference)) return false;
|
|
45264
|
+
if (!propsReference?.resolved || !isWholePropsObjectReference(analysis, propsReference) || hasMutableBindingWrite$1(propsReference) || propsReference.resolved.references.some((candidateReference) => candidateReference !== propsReference)) return false;
|
|
44821
45265
|
const contextExpression = stripParenExpression(contextSpread.argument);
|
|
44822
45266
|
if (!isNodeOfType(contextExpression, "Identifier")) return false;
|
|
44823
45267
|
const contextReference = getRef(analysis, contextExpression);
|
|
44824
|
-
if (!contextReference?.resolved || hasMutableBindingWrite(contextReference) || contextReference.resolved.references.some((candidateReference) => !candidateReference.init && candidateReference !== contextReference)) return false;
|
|
45268
|
+
if (!contextReference?.resolved || hasMutableBindingWrite$1(contextReference) || contextReference.resolved.references.some((candidateReference) => !candidateReference.init && candidateReference !== contextReference)) return false;
|
|
44825
45269
|
const contextInitializer = contextReference.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
44826
45270
|
if (!contextInitializer || !isNodeOfType(contextInitializer, "VariableDeclarator") || getDeclarationKind(contextInitializer) !== "const" || !contextInitializer.init || !isNodeOfType(contextInitializer.init, "CallExpression")) return false;
|
|
44827
45271
|
const contextHook = stripParenExpression(contextInitializer.init.callee);
|
|
@@ -44835,7 +45279,7 @@ const getImmutableParentCallbackPropName = (analysis, expression) => {
|
|
|
44835
45279
|
while (isNodeOfType(currentExpression, "Identifier")) {
|
|
44836
45280
|
const currentReference = getRef(analysis, currentExpression);
|
|
44837
45281
|
const currentVariable = currentReference?.resolved;
|
|
44838
|
-
if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return null;
|
|
45282
|
+
if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite$1(currentReference)) return null;
|
|
44839
45283
|
visitedVariables.add(currentVariable);
|
|
44840
45284
|
const definition = currentVariable.defs.length === 1 ? currentVariable.defs[0] : null;
|
|
44841
45285
|
const bindingIdentifier = definition?.name;
|
|
@@ -44904,7 +45348,7 @@ const getCommandCallbackPropName = (analysis, expression, isReactUseRefCall) =>
|
|
|
44904
45348
|
while (isNodeOfType(currentExpression, "Identifier")) {
|
|
44905
45349
|
const callbackReference = getRef(analysis, currentExpression);
|
|
44906
45350
|
const callbackVariable = callbackReference?.resolved;
|
|
44907
|
-
if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable) || hasMutableBindingWrite(callbackReference)) return null;
|
|
45351
|
+
if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable) || hasMutableBindingWrite$1(callbackReference)) return null;
|
|
44908
45352
|
visitedVariables.add(callbackVariable);
|
|
44909
45353
|
const definition = callbackVariable.defs.length === 1 ? callbackVariable.defs[0] : null;
|
|
44910
45354
|
const declarator = definition?.node;
|
|
@@ -44924,10 +45368,11 @@ const getCommandCallbackPropName = (analysis, expression, isReactUseRefCall) =>
|
|
|
44924
45368
|
if (!propertyName || !COMMAND_PROP_NAME_PATTERN.test(propertyName)) return null;
|
|
44925
45369
|
return refCurrentObjectPreservesCallbackProperty(analysis, currentExpression.object, propertyName, isReactUseRefCall) ? propertyName : null;
|
|
44926
45370
|
};
|
|
44927
|
-
const isWrapperHookCallbackRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
|
|
45371
|
+
const isWrapperHookCallbackRef = (analysis, ref, scopes) => Boolean(ref.resolved?.defs.some((def) => {
|
|
44928
45372
|
const node = def.node;
|
|
44929
45373
|
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
44930
|
-
|
|
45374
|
+
const resultSymbol = isNodeOfType(node.id, "Identifier") ? scopes.symbolFor(node.id) ?? null : null;
|
|
45375
|
+
return getWrapperHookWrappedFunction(unwrapChainExpression(node.init), resultSymbol, scopes) !== null;
|
|
44931
45376
|
}));
|
|
44932
45377
|
const isHandlerBagArgument = (analysis, argument) => {
|
|
44933
45378
|
if (!isNodeOfType(argument, "ObjectExpression")) return false;
|
|
@@ -44946,14 +45391,25 @@ const isHandlerBagArgument = (analysis, argument) => {
|
|
|
44946
45391
|
};
|
|
44947
45392
|
const getFunctionalUpdaterDataRefs = (analysis, updater) => getDownstreamRefs(analysis, updater).filter((updaterRef) => !updaterRef.resolved?.defs.some((def) => def.type === "Parameter" && def.node === updater));
|
|
44948
45393
|
const HOOK_NAME_PATTERN$1 = /^use[A-Z0-9]/;
|
|
44949
|
-
const EXTERNAL_SUBSCRIPTION_HOOK_NAMES = new Set([
|
|
45394
|
+
const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
|
|
44950
45395
|
"useIntersectionObserver",
|
|
44951
45396
|
"useMatchMedia",
|
|
45397
|
+
"useMediaJobProgress",
|
|
44952
45398
|
"useMediaQuery",
|
|
44953
45399
|
"useResizeObserver",
|
|
44954
45400
|
"useVisibility",
|
|
44955
45401
|
"useWindowSize"
|
|
44956
45402
|
]);
|
|
45403
|
+
const isCallbackPropReference = (analysis, ref) => {
|
|
45404
|
+
if (!isProp(analysis, ref)) return false;
|
|
45405
|
+
const identifier = ref.identifier;
|
|
45406
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
45407
|
+
if (!isWholePropsObjectReference(analysis, ref)) return HANDLER_NAMED_PROP_PATTERN.test(identifier.name);
|
|
45408
|
+
const member = identifier.parent;
|
|
45409
|
+
if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier) return false;
|
|
45410
|
+
const propertyName = getStaticMemberPropertyName(member);
|
|
45411
|
+
return Boolean(propertyName && HANDLER_NAMED_PROP_PATTERN.test(propertyName));
|
|
45412
|
+
};
|
|
44957
45413
|
const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
|
|
44958
45414
|
const node = def.node;
|
|
44959
45415
|
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
@@ -44961,7 +45417,7 @@ const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs
|
|
|
44961
45417
|
if (!isNodeOfType(init, "CallExpression")) return false;
|
|
44962
45418
|
const callee = init.callee;
|
|
44963
45419
|
if (!isNodeOfType(callee, "Identifier") || !HOOK_NAME_PATTERN$1.test(callee.name)) return false;
|
|
44964
|
-
return (init.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) =>
|
|
45420
|
+
return (init.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
|
|
44965
45421
|
}));
|
|
44966
45422
|
const isParentWiredHookResultArgument = (analysis, argument) => {
|
|
44967
45423
|
if (!isNodeOfType(argument, "Identifier")) return false;
|
|
@@ -44974,19 +45430,19 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
|
|
|
44974
45430
|
if (!isNodeOfType(identifier, "Identifier") || !HOOK_NAME_PATTERN$1.test(identifier.name)) return false;
|
|
44975
45431
|
const parent = identifier.parent;
|
|
44976
45432
|
if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
|
|
44977
|
-
return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) =>
|
|
45433
|
+
return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
|
|
44978
45434
|
};
|
|
44979
45435
|
const isExternalSubscriptionHookRef = (ref) => {
|
|
44980
45436
|
const identifier = ref.identifier;
|
|
44981
45437
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
44982
|
-
if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(identifier.name) && isCalleePosition(identifier)) return true;
|
|
45438
|
+
if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
|
|
44983
45439
|
return Boolean(ref.resolved?.defs.some((def) => {
|
|
44984
45440
|
const node = def.node;
|
|
44985
45441
|
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
44986
45442
|
const initializer = stripParenExpression(node.init);
|
|
44987
45443
|
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
44988
45444
|
const callee = stripParenExpression(initializer.callee);
|
|
44989
|
-
return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(callee.name);
|
|
45445
|
+
return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name);
|
|
44990
45446
|
}));
|
|
44991
45447
|
};
|
|
44992
45448
|
const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
|
|
@@ -45022,16 +45478,22 @@ const noPassDataToParent = defineRule({
|
|
|
45022
45478
|
const callExpr = getCallExpr(ref);
|
|
45023
45479
|
if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
|
|
45024
45480
|
const callbackRefProvenance = getCallbackRefProvenance(analysis, node, callExpr, isReactUseRefCall, isReactUseEffectCall);
|
|
45025
|
-
if (isRefCall(analysis, ref) && !callbackRefProvenance) continue;
|
|
45026
45481
|
if (!isSynchronous(ref.identifier, effectFn)) continue;
|
|
45027
45482
|
const calleeNode = unwrapChainExpression(callExpr.callee);
|
|
45028
45483
|
const identifier = ref.identifier;
|
|
45029
|
-
|
|
45030
|
-
|
|
45484
|
+
const resolvedCallbackPropNames = isNodeOfType(calleeNode, "MemberExpression") && getStaticMemberPropertyName(calleeNode) === "current" ? null : getParentCallbackPropNames({
|
|
45485
|
+
analysis,
|
|
45486
|
+
expression: calleeNode,
|
|
45487
|
+
scopes: context.scopes
|
|
45488
|
+
});
|
|
45489
|
+
const callbackPropNames = callbackRefProvenance?.callbackPropNames ?? resolvedCallbackPropNames;
|
|
45490
|
+
if (isRefCall(analysis, ref) && !callbackPropNames) continue;
|
|
45491
|
+
if (callbackPropNames) {
|
|
45492
|
+
if ([...callbackPropNames].some((callbackPropName) => COMMAND_PROP_NAME_PATTERN.test(callbackPropName))) continue;
|
|
45031
45493
|
} else if (calleeNode === identifier) {
|
|
45032
45494
|
const callbackPropName = getCommandCallbackPropName(analysis, identifier, isReactUseRefCall);
|
|
45033
45495
|
if (callbackPropName && COMMAND_PROP_NAME_PATTERN.test(callbackPropName)) continue;
|
|
45034
|
-
if (!isDirectParentCallbackRef(analysis, ref)) continue;
|
|
45496
|
+
if (!isDirectParentCallbackRef(analysis, ref, context.scopes)) continue;
|
|
45035
45497
|
if (isNodeOfType(identifier, "Identifier") && COMMAND_PROP_NAME_PATTERN.test(identifier.name)) continue;
|
|
45036
45498
|
} else if (isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === identifier) {
|
|
45037
45499
|
if (!isWholePropsObjectReference(analysis, ref)) continue;
|
|
@@ -45039,10 +45501,10 @@ const noPassDataToParent = defineRule({
|
|
|
45039
45501
|
} else continue;
|
|
45040
45502
|
const methodName = getCallMethodName(calleeNode);
|
|
45041
45503
|
const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
|
|
45042
|
-
if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
|
|
45504
|
+
if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead && !callbackPropNames) continue;
|
|
45043
45505
|
if (methodName && COMMAND_PROP_NAME_PATTERN.test(methodName)) continue;
|
|
45044
|
-
if (!
|
|
45045
|
-
const isSetterNamedCallee =
|
|
45506
|
+
if (!callbackPropNames && isNamespacedApiCallee(calleeNode)) continue;
|
|
45507
|
+
const isSetterNamedCallee = callbackPropNames ? [...callbackPropNames].every((callbackPropName) => SETTER_NAMED_PROP_PATTERN.test(callbackPropName)) : Boolean((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) && SETTER_NAMED_PROP_PATTERN.test((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) ?? ""));
|
|
45046
45508
|
const isLeafRef = (argRef) => getUpstreamRefs(analysis, argRef).length === 1;
|
|
45047
45509
|
const argsUpstreamRefs = (callExpr.arguments ?? []).flatMap((argument) => {
|
|
45048
45510
|
if (isFunctionLike$1(argument)) {
|
|
@@ -45057,7 +45519,7 @@ const noPassDataToParent = defineRule({
|
|
|
45057
45519
|
}
|
|
45058
45520
|
return getDownstreamRefs(analysis, argument);
|
|
45059
45521
|
}).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
|
|
45060
|
-
if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
|
|
45522
|
+
if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
|
|
45061
45523
|
if (!argsUpstreamRefs.some((argRef) => {
|
|
45062
45524
|
if (isUseStateIdentifier(argRef.identifier)) return false;
|
|
45063
45525
|
if (isExternalSubscriptionHookRef(argRef)) return false;
|
|
@@ -45095,9 +45557,47 @@ const isCallResultConsumedAsArgument = (callExpression) => {
|
|
|
45095
45557
|
return false;
|
|
45096
45558
|
};
|
|
45097
45559
|
//#endregion
|
|
45560
|
+
//#region src/plugin/rules/state-and-effects/utils/is-custom-hook-state-result-reference.ts
|
|
45561
|
+
const NON_STATE_CUSTOM_HOOK_NAMES = new Set([
|
|
45562
|
+
"useCallbackRef",
|
|
45563
|
+
"useEffectEvent",
|
|
45564
|
+
"useEvent",
|
|
45565
|
+
"useEventCallback",
|
|
45566
|
+
"useLatest",
|
|
45567
|
+
"useMemoizedFn",
|
|
45568
|
+
"useStableCallback"
|
|
45569
|
+
]);
|
|
45570
|
+
const EXTERNAL_SUBSCRIPTION_HOOK_NAMES = new Set([
|
|
45571
|
+
"useIntersectionObserver",
|
|
45572
|
+
"useMatchMedia",
|
|
45573
|
+
"useMediaJobProgress",
|
|
45574
|
+
"useMediaQuery",
|
|
45575
|
+
"useResizeObserver",
|
|
45576
|
+
"useVisibility",
|
|
45577
|
+
"useWindowSize"
|
|
45578
|
+
]);
|
|
45579
|
+
const getHookCalleeName = (initializer) => {
|
|
45580
|
+
const unwrappedInitializer = stripParenExpression(initializer);
|
|
45581
|
+
if (!isNodeOfType(unwrappedInitializer, "CallExpression")) return null;
|
|
45582
|
+
const callee = stripParenExpression(unwrappedInitializer.callee);
|
|
45583
|
+
if (isNodeOfType(callee, "Identifier")) return callee.name;
|
|
45584
|
+
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
|
|
45585
|
+
return null;
|
|
45586
|
+
};
|
|
45587
|
+
const isCustomHookStateResultReference = (analysis, reference) => Boolean(reference.resolved?.defs.some((definition) => {
|
|
45588
|
+
const declarator = definition.node;
|
|
45589
|
+
if (!isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return false;
|
|
45590
|
+
const calleeName = getHookCalleeName(declarator.init);
|
|
45591
|
+
if (!calleeName || !HOOK_NAME_PATTERN$3.test(calleeName) || BUILTIN_HOOK_NAMES.has(calleeName) || NON_STATE_CUSTOM_HOOK_NAMES.has(calleeName) || EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(calleeName)) return false;
|
|
45592
|
+
const initializer = stripParenExpression(declarator.init);
|
|
45593
|
+
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
45594
|
+
return initializer.arguments.some((argument) => getDownstreamRefs(analysis, argument).some((argumentReference) => isProp(analysis, argumentReference)));
|
|
45595
|
+
}));
|
|
45596
|
+
//#endregion
|
|
45098
45597
|
//#region src/plugin/rules/state-and-effects/no-pass-live-state-to-parent.ts
|
|
45099
45598
|
const SETTER_NAMED_CALLBACK_PATTERN = /^set[A-Z]/;
|
|
45100
45599
|
const DATA_FETCHING_CALLBACK_PATTERN = /^(fetch|refetch|load|query|request)([A-Z_]|$)/;
|
|
45600
|
+
const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
45101
45601
|
const getCallCalleeName = (callExpr) => {
|
|
45102
45602
|
if (!isNodeOfType(callExpr, "CallExpression")) return null;
|
|
45103
45603
|
const callee = callExpr.callee;
|
|
@@ -45142,6 +45642,10 @@ const collectUpstreamStateRefs = (analysis, ref, stateRefs, visited) => {
|
|
|
45142
45642
|
stateRefs.push(ref);
|
|
45143
45643
|
return;
|
|
45144
45644
|
}
|
|
45645
|
+
if (isCustomHookStateResultReference(analysis, ref)) {
|
|
45646
|
+
stateRefs.push(ref);
|
|
45647
|
+
return;
|
|
45648
|
+
}
|
|
45145
45649
|
for (const def of ref.resolved?.defs ?? []) {
|
|
45146
45650
|
if (def.type === "ImportBinding" || def.type === "Parameter") continue;
|
|
45147
45651
|
const defNode = def.node;
|
|
@@ -45171,6 +45675,32 @@ const collectPropCallbackBoundStateRefs = (analysis, ref, isPropCallbackRef) =>
|
|
|
45171
45675
|
}
|
|
45172
45676
|
return stateRefs;
|
|
45173
45677
|
};
|
|
45678
|
+
const collectDirectCallStateRefs = (analysis, callExpression) => {
|
|
45679
|
+
const stateReferences = [];
|
|
45680
|
+
for (const argument of callExpression.arguments) {
|
|
45681
|
+
if (isFunctionLike$1(argument)) continue;
|
|
45682
|
+
for (const argumentReference of getDownstreamRefs(analysis, argument)) {
|
|
45683
|
+
if (resolveToFunction(argumentReference)) continue;
|
|
45684
|
+
collectUpstreamStateRefs(analysis, argumentReference, stateReferences, /* @__PURE__ */ new Set());
|
|
45685
|
+
}
|
|
45686
|
+
}
|
|
45687
|
+
return stateReferences;
|
|
45688
|
+
};
|
|
45689
|
+
const getTransparentWrapperPropReference = (analysis, reference, context) => {
|
|
45690
|
+
for (const definition of reference.resolved?.defs ?? []) {
|
|
45691
|
+
const declarator = definition.node;
|
|
45692
|
+
if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.id, "Identifier") || !declarator.init) continue;
|
|
45693
|
+
const resultSymbol = context.scopes.symbolFor(declarator.id);
|
|
45694
|
+
const callbackArgument = getTransparentReactCallbackWrapperArgument(declarator.init, resultSymbol, context.scopes);
|
|
45695
|
+
if (!callbackArgument) continue;
|
|
45696
|
+
const callbackReferences = getDownstreamRefs(analysis, callbackArgument);
|
|
45697
|
+
const callbackReference = callbackReferences.find((candidateReference) => isPropCallbackInvocationRef(analysis, candidateReference));
|
|
45698
|
+
if (callbackReference) return callbackReference;
|
|
45699
|
+
const propReference = callbackReferences.find((candidateReference) => isProp(analysis, candidateReference) && !candidateReference.resolved?.references.some((candidateUsage) => candidateUsage.isWrite() && !candidateUsage.init));
|
|
45700
|
+
if (propReference) return propReference;
|
|
45701
|
+
}
|
|
45702
|
+
return null;
|
|
45703
|
+
};
|
|
45174
45704
|
const isSetterNamedCallbackReceivingData = (callbackRef) => {
|
|
45175
45705
|
const callExpr = getCallExpr(callbackRef);
|
|
45176
45706
|
if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) return false;
|
|
@@ -45206,6 +45736,16 @@ const resolvesToLocalHookReturnBinding = (ref) => Boolean(ref?.resolved?.defs?.s
|
|
|
45206
45736
|
const calleeName = getInitializerCalleeName(node.init);
|
|
45207
45737
|
return calleeName !== null && isReactHookName(calleeName) && !FUNCTION_WRAPPER_HOOK_NAMES.has(calleeName);
|
|
45208
45738
|
}));
|
|
45739
|
+
const getDirectLocalEffectHelper = (callExpression, effectFunction, context) => {
|
|
45740
|
+
const helperFunction = resolveExactLocalFunction(callExpression.callee, context.scopes);
|
|
45741
|
+
if (!helperFunction) return null;
|
|
45742
|
+
let ancestor = callExpression.parent;
|
|
45743
|
+
while (ancestor && ancestor !== effectFunction) {
|
|
45744
|
+
if (isFunctionLike$1(ancestor)) return null;
|
|
45745
|
+
ancestor = ancestor.parent;
|
|
45746
|
+
}
|
|
45747
|
+
return ancestor === effectFunction ? helperFunction : null;
|
|
45748
|
+
};
|
|
45209
45749
|
const noPassLiveStateToParent = defineRule({
|
|
45210
45750
|
id: "no-pass-live-state-to-parent",
|
|
45211
45751
|
title: "Live state pushed to parent via effect",
|
|
@@ -45220,20 +45760,32 @@ const noPassLiveStateToParent = defineRule({
|
|
|
45220
45760
|
if (!effectFnRefs) return;
|
|
45221
45761
|
const effectFn = getEffectFn(analysis, node);
|
|
45222
45762
|
if (!effectFn) return;
|
|
45763
|
+
const effectFunctionBody = isNodeOfType(effectFn, "ArrowFunctionExpression") || isNodeOfType(effectFn, "FunctionExpression") || isNodeOfType(effectFn, "FunctionDeclaration") ? effectFn.body : null;
|
|
45223
45764
|
for (const ref of effectFnRefs) {
|
|
45224
|
-
const propCallbackRefs = getEventualCallRefsTo(analysis, ref, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef));
|
|
45225
|
-
if (propCallbackRefs.length === 0) continue;
|
|
45226
|
-
if (resolvesToLocalHookReturnBinding(ref)) continue;
|
|
45227
|
-
if (!isSynchronous(ref.identifier, effectFn)) continue;
|
|
45228
45765
|
const callExpr = getCallExpr(ref);
|
|
45229
|
-
if (!callExpr) continue;
|
|
45766
|
+
if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
|
|
45767
|
+
const directLocalEffectHelper = getDirectLocalEffectHelper(callExpr, effectFn, context);
|
|
45768
|
+
const callGraphReferences = directLocalEffectHelper ? [ref, ...getDownstreamRefs(analysis, directLocalEffectHelper)] : [ref];
|
|
45769
|
+
const resolvedCallbackPropNames = getParentCallbackPropNames({
|
|
45770
|
+
analysis,
|
|
45771
|
+
expression: callExpr.callee,
|
|
45772
|
+
scopes: context.scopes
|
|
45773
|
+
});
|
|
45774
|
+
const callExpressionRoot = findTransparentExpressionRoot(callExpr);
|
|
45775
|
+
const notificationCallbackPropNames = Boolean(resolvedCallbackPropNames && callExpr.arguments.length > 0 && (!isCallResultCapturedToLocal(callExpr) || isNodeOfType(callExpressionRoot.parent, "ReturnStatement") && callExpressionRoot.parent.parent === effectFunctionBody) && [...resolvedCallbackPropNames].every((callbackPropName) => !DATA_FETCHING_CALLBACK_PATTERN.test(callbackPropName))) ? resolvedCallbackPropNames : null;
|
|
45776
|
+
if (!notificationCallbackPropNames && hasMutableBindingWrite(ref)) continue;
|
|
45777
|
+
const propCallbackRefs = callGraphReferences.flatMap((callGraphReference) => getEventualCallRefsTo(analysis, callGraphReference, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef)));
|
|
45778
|
+
const transparentPropReference = propCallbackRefs.length === 0 ? getTransparentWrapperPropReference(analysis, ref, context) : null;
|
|
45779
|
+
if (propCallbackRefs.length === 0 && !transparentPropReference && !notificationCallbackPropNames) continue;
|
|
45780
|
+
if (!notificationCallbackPropNames && resolvesToLocalHookReturnBinding(ref)) continue;
|
|
45781
|
+
if (!isSynchronous(ref.identifier, effectFn) && !directLocalEffectHelper) continue;
|
|
45230
45782
|
if (isCallResultConsumedAsArgument(callExpr)) continue;
|
|
45231
45783
|
const calleeNode = callExpr.callee;
|
|
45232
45784
|
const methodName = calleeNode ? getCallMethodName(calleeNode) : null;
|
|
45233
45785
|
const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && calleeNode && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
|
|
45234
|
-
if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
|
|
45235
|
-
if (calleeNode && isNamespacedApiCallee(calleeNode)) continue;
|
|
45236
|
-
const stateArgRefs = collectPropCallbackBoundStateRefs(analysis,
|
|
45786
|
+
if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead && !notificationCallbackPropNames) continue;
|
|
45787
|
+
if (!notificationCallbackPropNames && calleeNode && isNamespacedApiCallee(calleeNode)) continue;
|
|
45788
|
+
const stateArgRefs = transparentPropReference || notificationCallbackPropNames ? collectDirectCallStateRefs(analysis, callExpr) : callGraphReferences.flatMap((callGraphReference) => collectPropCallbackBoundStateRefs(analysis, callGraphReference, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef)));
|
|
45237
45789
|
const handsSetterNamedCallbackData = propCallbackRefs.some(isSetterNamedCallbackReceivingData);
|
|
45238
45790
|
if (stateArgRefs.length === 0 && !handsSetterNamedCallbackData) continue;
|
|
45239
45791
|
context.report({
|
|
@@ -45626,6 +46178,7 @@ const isStateLikeDependency = (analysis, element, isPropName) => {
|
|
|
45626
46178
|
if (!analysis) return true;
|
|
45627
46179
|
const reference = getRef(analysis, element);
|
|
45628
46180
|
if (!reference) return true;
|
|
46181
|
+
if (isCustomHookStateResultReference(analysis, reference)) return true;
|
|
45629
46182
|
const upstreamReferences = getUpstreamRefs(analysis, reference);
|
|
45630
46183
|
if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
|
|
45631
46184
|
return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
|
|
@@ -45642,6 +46195,22 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
|
|
|
45642
46195
|
if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
|
|
45643
46196
|
return isPropName(callbackArgument.name) ? callbackArgument.name : null;
|
|
45644
46197
|
};
|
|
46198
|
+
const getTransparentWrappedPropCallbackName = (callExpression, context, isPropName) => {
|
|
46199
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
46200
|
+
if (!isNodeOfType(callee, "Identifier")) return null;
|
|
46201
|
+
const binding = findVariableInitializer(callExpression, callee.name);
|
|
46202
|
+
if (!binding?.initializer) return null;
|
|
46203
|
+
const resultSymbol = context.scopes.symbolFor(callee);
|
|
46204
|
+
const callbackArgument = getTransparentReactCallbackWrapperArgument(binding.initializer, resultSymbol, context.scopes);
|
|
46205
|
+
if (!callbackArgument) return null;
|
|
46206
|
+
const callbackSource = stripParenExpression(callbackArgument);
|
|
46207
|
+
if (isNodeOfType(callbackSource, "Identifier")) return isPropName(callbackSource.name, callbackSource) ? callbackSource.name : null;
|
|
46208
|
+
if (!isNodeOfType(callbackSource, "MemberExpression")) return null;
|
|
46209
|
+
const receiver = stripParenExpression(callbackSource.object);
|
|
46210
|
+
const propertyName = getStaticPropertyName(callbackSource);
|
|
46211
|
+
if (!isNodeOfType(receiver, "Identifier") || !propertyName) return null;
|
|
46212
|
+
return isPropName(receiver.name, receiver) ? propertyName : null;
|
|
46213
|
+
};
|
|
45645
46214
|
const noPropCallbackInEffect = defineRule({
|
|
45646
46215
|
id: "no-prop-callback-in-effect",
|
|
45647
46216
|
title: "Parent kept in sync with a callback effect",
|
|
@@ -45675,9 +46244,16 @@ const noPropCallbackInEffect = defineRule({
|
|
|
45675
46244
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
45676
46245
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
45677
46246
|
const directCallee = stripParenExpression(child.callee);
|
|
45678
|
-
const
|
|
46247
|
+
const resolvedCallbackPropNames = analysis && propStackTracker.getCurrentPropNames().size > 0 ? getParentCallbackPropNames({
|
|
46248
|
+
analysis,
|
|
46249
|
+
expression: directCallee,
|
|
46250
|
+
scopes: context.scopes
|
|
46251
|
+
}) : null;
|
|
46252
|
+
const calleeName = resolvedCallbackPropNames && [...resolvedCallbackPropNames][0] || isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName) || getTransparentWrappedPropCallbackName(child, context, propStackTracker.isPropName);
|
|
45679
46253
|
if (!calleeName) return;
|
|
45680
|
-
|
|
46254
|
+
const callExpressionRoot = findTransparentExpressionRoot(child);
|
|
46255
|
+
const isDirectEffectReturn = isNodeOfType(callExpressionRoot.parent, "ReturnStatement") && callExpressionRoot.parent.parent === callback.body;
|
|
46256
|
+
if (!isResultDiscardedCall(child) && !isDirectEffectReturn) return;
|
|
45681
46257
|
if (reportedNodes.has(child)) return;
|
|
45682
46258
|
reportedNodes.add(child);
|
|
45683
46259
|
context.report({
|
|
@@ -56471,8 +57047,39 @@ const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node,
|
|
|
56471
57047
|
destructureIndex: 1
|
|
56472
57048
|
});
|
|
56473
57049
|
//#endregion
|
|
57050
|
+
//#region src/plugin/utils/unwrap-return-expression.ts
|
|
57051
|
+
const unwrapReturnExpression = (node) => isNodeOfType(node, "ReturnStatement") && node.argument ? node.argument : node;
|
|
57052
|
+
//#endregion
|
|
56474
57053
|
//#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
|
|
56475
57054
|
const USE_EFFECT_ONLY = new Set(["useEffect"]);
|
|
57055
|
+
const USE_CALLBACK_ONLY = new Set(["useCallback"]);
|
|
57056
|
+
const USE_STATE_ONLY = new Set(["useState"]);
|
|
57057
|
+
const REACT_API_CALL_OPTIONS = {
|
|
57058
|
+
allowGlobalReactNamespace: true,
|
|
57059
|
+
allowUnboundBareCalls: true,
|
|
57060
|
+
resolveNamedAliases: true
|
|
57061
|
+
};
|
|
57062
|
+
const expressionReadsDerivedSymbol = (context, expression, stateDerivedSymbolIds) => {
|
|
57063
|
+
let readsDerivedSymbol = false;
|
|
57064
|
+
walkAst(expression, (node) => {
|
|
57065
|
+
if (readsDerivedSymbol) return false;
|
|
57066
|
+
if (node !== expression && isFunctionLike$1(node)) return false;
|
|
57067
|
+
if (isNodeOfType(node, "Identifier") && stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) readsDerivedSymbol = true;
|
|
57068
|
+
});
|
|
57069
|
+
return readsDerivedSymbol;
|
|
57070
|
+
};
|
|
57071
|
+
const getStaticObjectPropertyName = (property) => {
|
|
57072
|
+
if (!isNodeOfType(property, "Property") || property.computed || property.method || property.kind !== "init") return null;
|
|
57073
|
+
if (isNodeOfType(property.key, "Identifier")) return property.key.name;
|
|
57074
|
+
if (isNodeOfType(property.key, "Literal") && (typeof property.key.value === "string" || typeof property.key.value === "number")) return String(property.key.value);
|
|
57075
|
+
return null;
|
|
57076
|
+
};
|
|
57077
|
+
const isNonVisibleJsxSpreadProperty = (propertyName) => propertyName === "id" || propertyName.startsWith("aria-") || /^on[A-Z]/.test(propertyName);
|
|
57078
|
+
const isTransparentAssignmentTarget = (identifier) => {
|
|
57079
|
+
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
57080
|
+
const parent = expressionRoot.parent;
|
|
57081
|
+
return Boolean(isNodeOfType(parent, "AssignmentExpression") && parent.left === expressionRoot || isNodeOfType(parent, "UpdateExpression") && parent.argument === expressionRoot || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expressionRoot);
|
|
57082
|
+
};
|
|
56476
57083
|
const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
|
|
56477
57084
|
let readsCurrent = false;
|
|
56478
57085
|
walkAst(argument, (child) => {
|
|
@@ -56524,6 +57131,166 @@ const isStateUsedOnlyInIdOrAriaAttributes = (setterCall, setterName) => {
|
|
|
56524
57131
|
});
|
|
56525
57132
|
return referenceCount > 0 && !nonAriaReferenceFound;
|
|
56526
57133
|
};
|
|
57134
|
+
const isGlobalWindowMember = (context, node, propertyName) => {
|
|
57135
|
+
const member = stripParenExpression(node);
|
|
57136
|
+
if (!isNodeOfType(member, "MemberExpression") || member.computed) return false;
|
|
57137
|
+
const receiver = stripParenExpression(member.object);
|
|
57138
|
+
return isNodeOfType(receiver, "Identifier") && receiver.name === "window" && context.scopes.isGlobalReference(receiver) && isNodeOfType(member.property, "Identifier") && member.property.name === propertyName;
|
|
57139
|
+
};
|
|
57140
|
+
const getDirectWindowWidthSetter = (context, statement) => {
|
|
57141
|
+
const call = unwrapDiscardedExpression(statement);
|
|
57142
|
+
if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 1) return null;
|
|
57143
|
+
if (!isNodeOfType(call.callee, "Identifier") || !isSetterCall(call)) return null;
|
|
57144
|
+
const argument = call.arguments[0];
|
|
57145
|
+
return isGlobalWindowMember(context, argument, "innerWidth") ? call : null;
|
|
57146
|
+
};
|
|
57147
|
+
const getResizeListenerHandler = (context, statement, methodName) => {
|
|
57148
|
+
const call = unwrapDiscardedExpression(statement);
|
|
57149
|
+
if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 2) return null;
|
|
57150
|
+
if (!isGlobalWindowMember(context, call.callee, methodName)) return null;
|
|
57151
|
+
const eventName = call.arguments[0];
|
|
57152
|
+
const handler = call.arguments[1];
|
|
57153
|
+
if (!isNodeOfType(eventName, "Literal") || eventName.value !== "resize") return null;
|
|
57154
|
+
return isNodeOfType(handler, "Identifier") ? handler : null;
|
|
57155
|
+
};
|
|
57156
|
+
const getCleanupResizeHandler = (context, statement) => {
|
|
57157
|
+
if (!isNodeOfType(statement, "ReturnStatement") || !isFunctionLike$1(statement.argument)) return null;
|
|
57158
|
+
const cleanupStatements = getCallbackStatements(statement.argument);
|
|
57159
|
+
if (cleanupStatements.length !== 1) return null;
|
|
57160
|
+
return getResizeListenerHandler(context, unwrapReturnExpression(cleanupStatements[0]), "removeEventListener");
|
|
57161
|
+
};
|
|
57162
|
+
const findExactViewportState = (context, componentFunction, setterCall) => {
|
|
57163
|
+
if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
|
|
57164
|
+
const componentBody = componentFunction.body;
|
|
57165
|
+
if (!isNodeOfType(setterCall.callee, "Identifier")) return null;
|
|
57166
|
+
const setterSymbol = context.scopes.symbolFor(setterCall.callee);
|
|
57167
|
+
if (!setterSymbol || setterSymbol.kind !== "const" || !isNodeOfType(setterSymbol.declarationNode, "VariableDeclarator")) return null;
|
|
57168
|
+
const declarator = setterSymbol.declarationNode;
|
|
57169
|
+
if (!isNodeOfType(declarator.id, "ArrayPattern")) return null;
|
|
57170
|
+
const stateIdentifier = declarator.id.elements?.[0];
|
|
57171
|
+
const setterIdentifier = declarator.id.elements?.[1];
|
|
57172
|
+
if (!isNodeOfType(stateIdentifier, "Identifier") || !isNodeOfType(setterIdentifier, "Identifier") || setterIdentifier !== setterSymbol.bindingIdentifier || !isNodeOfType(declarator.init, "CallExpression") || !isReactApiCall(declarator.init, USE_STATE_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return null;
|
|
57173
|
+
const initializer = declarator.init.arguments?.[0];
|
|
57174
|
+
if (!isNodeOfType(initializer, "Literal") || initializer.value !== 0) return null;
|
|
57175
|
+
const stateSymbol = context.scopes.symbolFor(stateIdentifier);
|
|
57176
|
+
if (!stateSymbol) return null;
|
|
57177
|
+
const stateDerivedSymbolIds = new Set([stateSymbol.id]);
|
|
57178
|
+
let didAddDerivedSymbol = true;
|
|
57179
|
+
while (didAddDerivedSymbol) {
|
|
57180
|
+
didAddDerivedSymbol = false;
|
|
57181
|
+
for (const statement of componentBody.body ?? []) {
|
|
57182
|
+
if (!isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
57183
|
+
for (const candidateDeclarator of statement.declarations ?? []) {
|
|
57184
|
+
if (!isNodeOfType(candidateDeclarator.id, "Identifier") || !candidateDeclarator.init) continue;
|
|
57185
|
+
const candidateInitializer = stripParenExpression(candidateDeclarator.init);
|
|
57186
|
+
if (isFunctionLike$1(candidateInitializer) || isNodeOfType(candidateInitializer, "CallExpression") && isReactApiCall(candidateInitializer, USE_CALLBACK_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) continue;
|
|
57187
|
+
if (!expressionReadsDerivedSymbol(context, candidateInitializer, stateDerivedSymbolIds)) continue;
|
|
57188
|
+
const candidateSymbol = context.scopes.symbolFor(candidateDeclarator.id);
|
|
57189
|
+
if (candidateSymbol?.kind === "const" && candidateSymbol.references.every((reference) => reference.flag === "read" && !isTransparentAssignmentTarget(reference.identifier)) && !stateDerivedSymbolIds.has(candidateSymbol.id)) {
|
|
57190
|
+
stateDerivedSymbolIds.add(candidateSymbol.id);
|
|
57191
|
+
didAddDerivedSymbol = true;
|
|
57192
|
+
}
|
|
57193
|
+
}
|
|
57194
|
+
}
|
|
57195
|
+
}
|
|
57196
|
+
const staticSpreadVisibilityBySymbolId = /* @__PURE__ */ new Map();
|
|
57197
|
+
const hasOnlyStaticObjectReferences = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
57198
|
+
const symbol = context.scopes.symbolFor(identifier);
|
|
57199
|
+
if (!symbol) return false;
|
|
57200
|
+
if (visitedSymbolIds.has(symbol.id)) return true;
|
|
57201
|
+
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
57202
|
+
nextVisitedSymbolIds.add(symbol.id);
|
|
57203
|
+
let hasUnknownReference = false;
|
|
57204
|
+
walkAst(componentBody, (node) => {
|
|
57205
|
+
if (hasUnknownReference || !isNodeOfType(node, "Identifier") || context.scopes.symbolFor(node)?.id !== symbol.id || node === symbol.bindingIdentifier) return;
|
|
57206
|
+
const referenceRoot = findTransparentExpressionRoot(node);
|
|
57207
|
+
const parent = referenceRoot.parent;
|
|
57208
|
+
if (isNodeOfType(parent, "JSXSpreadAttribute") && parent.argument === referenceRoot) return;
|
|
57209
|
+
if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier") && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const" && hasOnlyStaticObjectReferences(parent.id, nextVisitedSymbolIds)) return;
|
|
57210
|
+
hasUnknownReference = true;
|
|
57211
|
+
return false;
|
|
57212
|
+
});
|
|
57213
|
+
return !hasUnknownReference;
|
|
57214
|
+
};
|
|
57215
|
+
const classifyStaticSpreadObject = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
57216
|
+
const symbol = context.scopes.symbolFor(identifier);
|
|
57217
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return "unknown";
|
|
57218
|
+
const cachedVisibility = staticSpreadVisibilityBySymbolId.get(symbol.id);
|
|
57219
|
+
if (cachedVisibility) return cachedVisibility;
|
|
57220
|
+
if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || symbol.declarationNode.id !== symbol.bindingIdentifier || !symbol.declarationNode.init) return "unknown";
|
|
57221
|
+
if (!hasOnlyStaticObjectReferences(identifier)) return "unknown";
|
|
57222
|
+
const initializer = stripParenExpression(symbol.declarationNode.init);
|
|
57223
|
+
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
57224
|
+
nextVisitedSymbolIds.add(symbol.id);
|
|
57225
|
+
if (isNodeOfType(initializer, "Identifier")) {
|
|
57226
|
+
const visibility = classifyStaticSpreadObject(initializer, nextVisitedSymbolIds);
|
|
57227
|
+
staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
|
|
57228
|
+
return visibility;
|
|
57229
|
+
}
|
|
57230
|
+
if (!isNodeOfType(initializer, "ObjectExpression")) return "unknown";
|
|
57231
|
+
let visibility = "non-visible";
|
|
57232
|
+
for (const property of initializer.properties ?? []) {
|
|
57233
|
+
const propertyName = getStaticObjectPropertyName(property);
|
|
57234
|
+
if (!isNodeOfType(property, "Property") || !propertyName) {
|
|
57235
|
+
visibility = "unknown";
|
|
57236
|
+
break;
|
|
57237
|
+
}
|
|
57238
|
+
if (expressionReadsDerivedSymbol(context, property.value, stateDerivedSymbolIds) && !isNonVisibleJsxSpreadProperty(propertyName)) visibility = "visible";
|
|
57239
|
+
}
|
|
57240
|
+
staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
|
|
57241
|
+
return visibility;
|
|
57242
|
+
};
|
|
57243
|
+
let hasNonAriaReference = false;
|
|
57244
|
+
walkAst(componentBody, (node) => {
|
|
57245
|
+
if (hasNonAriaReference) return false;
|
|
57246
|
+
if (!isNodeOfType(node, "Identifier") || !stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) return;
|
|
57247
|
+
if (findEnclosingFunction$1(node) !== componentFunction) return;
|
|
57248
|
+
const parent = node.parent;
|
|
57249
|
+
if (parent && (isNodeOfType(parent, "MemberExpression") && parent.property === node && !parent.computed || isNodeOfType(parent, "Property") && parent.key === node && !parent.computed)) return;
|
|
57250
|
+
let cursor = parent;
|
|
57251
|
+
while (cursor && cursor !== componentBody) {
|
|
57252
|
+
if (isFunctionLike$1(cursor)) return;
|
|
57253
|
+
if (isNodeOfType(cursor, "JSXSpreadAttribute")) {
|
|
57254
|
+
if (isNodeOfType(node, "Identifier") && classifyStaticSpreadObject(node) === "visible") hasNonAriaReference = true;
|
|
57255
|
+
return;
|
|
57256
|
+
}
|
|
57257
|
+
if (isNodeOfType(cursor, "JSXAttribute")) {
|
|
57258
|
+
if (isEventHandlerAttribute(cursor)) return;
|
|
57259
|
+
if (!isInsideIdOrAriaAttribute(node)) hasNonAriaReference = true;
|
|
57260
|
+
return;
|
|
57261
|
+
}
|
|
57262
|
+
if (isNodeOfType(cursor, "ReturnStatement")) {
|
|
57263
|
+
hasNonAriaReference = true;
|
|
57264
|
+
return;
|
|
57265
|
+
}
|
|
57266
|
+
cursor = cursor.parent;
|
|
57267
|
+
}
|
|
57268
|
+
});
|
|
57269
|
+
return hasNonAriaReference ? stateIdentifier.name : null;
|
|
57270
|
+
};
|
|
57271
|
+
const isExactViewportSubscriptionEffect = (context, effectCall, callback) => {
|
|
57272
|
+
if (!isReactApiCall(effectCall, USE_EFFECT_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return false;
|
|
57273
|
+
if (!isFunctionLike$1(callback) || callback.async || !isNodeOfType(callback.body, "BlockStatement")) return false;
|
|
57274
|
+
const statements = getCallbackStatements(callback);
|
|
57275
|
+
if (statements.length !== 4) return false;
|
|
57276
|
+
const handlerDeclaration = statements[0];
|
|
57277
|
+
if (!isNodeOfType(handlerDeclaration, "VariableDeclaration") || handlerDeclaration.kind !== "const" || handlerDeclaration.declarations?.length !== 1) return false;
|
|
57278
|
+
const handlerDeclarator = handlerDeclaration.declarations[0];
|
|
57279
|
+
if (!isNodeOfType(handlerDeclarator.id, "Identifier") || !isFunctionLike$1(handlerDeclarator.init)) return false;
|
|
57280
|
+
const handlerStatements = getCallbackStatements(handlerDeclarator.init);
|
|
57281
|
+
if (handlerStatements.length !== 1) return false;
|
|
57282
|
+
const handlerSetter = getDirectWindowWidthSetter(context, unwrapReturnExpression(handlerStatements[0]));
|
|
57283
|
+
const subscribedHandler = getResizeListenerHandler(context, statements[1], "addEventListener");
|
|
57284
|
+
const immediateSetter = getDirectWindowWidthSetter(context, statements[2]);
|
|
57285
|
+
const cleanupHandler = getCleanupResizeHandler(context, statements[3]);
|
|
57286
|
+
if (!handlerSetter || !subscribedHandler || !immediateSetter || !cleanupHandler) return false;
|
|
57287
|
+
const handlerSymbol = context.scopes.symbolFor(handlerDeclarator.id);
|
|
57288
|
+
if (!handlerSymbol || context.scopes.symbolFor(subscribedHandler) !== handlerSymbol || context.scopes.symbolFor(cleanupHandler) !== handlerSymbol) return false;
|
|
57289
|
+
if (!isNodeOfType(handlerSetter.callee, "Identifier") || !isNodeOfType(immediateSetter.callee, "Identifier") || context.scopes.symbolFor(handlerSetter.callee) !== context.scopes.symbolFor(immediateSetter.callee)) return false;
|
|
57290
|
+
const componentFunction = findEnclosingFunction$1(effectCall);
|
|
57291
|
+
if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return false;
|
|
57292
|
+
return findExactViewportState(context, componentFunction, immediateSetter) !== null;
|
|
57293
|
+
};
|
|
56527
57294
|
const renderingHydrationNoFlicker = defineRule({
|
|
56528
57295
|
id: "rendering-hydration-no-flicker",
|
|
56529
57296
|
title: "useEffect setState flashes on mount",
|
|
@@ -56536,7 +57303,14 @@ const renderingHydrationNoFlicker = defineRule({
|
|
|
56536
57303
|
if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
|
|
56537
57304
|
const callback = getEffectCallback(node);
|
|
56538
57305
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
56539
|
-
|
|
57306
|
+
if (isExactViewportSubscriptionEffect(context, node, callback)) {
|
|
57307
|
+
context.report({
|
|
57308
|
+
node,
|
|
57309
|
+
message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
|
|
57310
|
+
});
|
|
57311
|
+
return;
|
|
57312
|
+
}
|
|
57313
|
+
const bodyStatements = getCallbackStatements(callback);
|
|
56540
57314
|
if (bodyStatements.length !== 1) return;
|
|
56541
57315
|
const soleStatement = bodyStatements[0];
|
|
56542
57316
|
if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
|
|
@@ -66368,14 +67142,6 @@ const isStateKey = (key) => {
|
|
|
66368
67142
|
if (isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value === "state";
|
|
66369
67143
|
return false;
|
|
66370
67144
|
};
|
|
66371
|
-
const findEnclosingClass = (node) => {
|
|
66372
|
-
let ancestor = node.parent;
|
|
66373
|
-
while (ancestor) {
|
|
66374
|
-
if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
|
|
66375
|
-
ancestor = ancestor.parent ?? null;
|
|
66376
|
-
}
|
|
66377
|
-
return null;
|
|
66378
|
-
};
|
|
66379
67145
|
const isInConstructor = (node) => {
|
|
66380
67146
|
let ancestor = node.parent;
|
|
66381
67147
|
while (ancestor) {
|