oxlint-plugin-react-doctor 0.7.9-dev.0ff57fa → 0.7.9-dev.11388bf
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 +90 -781
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7637,7 +7637,6 @@ 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;
|
|
7641
7640
|
if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
|
|
7642
7641
|
if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
|
|
7643
7642
|
if (a.computed !== b.computed) return false;
|
|
@@ -8959,7 +8958,7 @@ const isReactNamespaceImport = (identifier, scopes) => {
|
|
|
8959
8958
|
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
8960
8959
|
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
8961
8960
|
};
|
|
8962
|
-
const isReactNamespaceReceiver
|
|
8961
|
+
const isReactNamespaceReceiver = (receiver, scopes, options) => {
|
|
8963
8962
|
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
8964
8963
|
if (isReactNamespaceImport(receiver, scopes)) return true;
|
|
8965
8964
|
return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
|
|
@@ -8972,7 +8971,7 @@ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) =>
|
|
|
8972
8971
|
for (const property of pattern.properties) {
|
|
8973
8972
|
if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
|
|
8974
8973
|
const propertyName = getStaticPropertyKeyName(property);
|
|
8975
|
-
return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver
|
|
8974
|
+
return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver(stripParenExpression(symbol.initializer), scopes, options));
|
|
8976
8975
|
}
|
|
8977
8976
|
return false;
|
|
8978
8977
|
};
|
|
@@ -8996,7 +8995,7 @@ const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds
|
|
|
8996
8995
|
return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
|
|
8997
8996
|
}
|
|
8998
8997
|
if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
|
|
8999
|
-
return isReactNamespaceReceiver
|
|
8998
|
+
return isReactNamespaceReceiver(stripParenExpression(callee.object), scopes, options);
|
|
9000
8999
|
};
|
|
9001
9000
|
//#endregion
|
|
9002
9001
|
//#region src/plugin/utils/is-proven-browser-api-receiver.ts
|
|
@@ -29422,7 +29421,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
29422
29421
|
//#endregion
|
|
29423
29422
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
29424
29423
|
const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
29425
|
-
const isUndefinedIdentifier
|
|
29424
|
+
const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
29426
29425
|
const noAccessKey = defineRule({
|
|
29427
29426
|
id: "no-access-key",
|
|
29428
29427
|
title: "accessKey attribute used",
|
|
@@ -29447,7 +29446,7 @@ const noAccessKey = defineRule({
|
|
|
29447
29446
|
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
29448
29447
|
const expression = attributeValue.expression;
|
|
29449
29448
|
if (!expression || expression.type === "JSXEmptyExpression") return;
|
|
29450
|
-
if (isUndefinedIdentifier
|
|
29449
|
+
if (isUndefinedIdentifier(expression)) return;
|
|
29451
29450
|
context.report({
|
|
29452
29451
|
node: accessKey,
|
|
29453
29452
|
message: MESSAGE$39
|
|
@@ -30212,12 +30211,6 @@ const isReactNamespaceImportReference = (ref) => Boolean(ref?.resolved?.defs.som
|
|
|
30212
30211
|
const importDeclaration = declarationNode.parent;
|
|
30213
30212
|
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
|
|
30214
30213
|
}));
|
|
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
|
-
};
|
|
30221
30214
|
const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
|
|
30222
30215
|
if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) return false;
|
|
30223
30216
|
const callee = stripParenExpression(declarator.init.callee);
|
|
@@ -30226,20 +30219,24 @@ const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
|
|
|
30226
30219
|
if (!reference?.resolved) return callee.name === hookName;
|
|
30227
30220
|
return isReactNamedImportReference(reference, hookName);
|
|
30228
30221
|
}
|
|
30229
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
|
|
30230
|
-
|
|
30222
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
|
|
30223
|
+
const namespaceReference = getRef(analysis, callee.object);
|
|
30224
|
+
if (!namespaceReference?.resolved) return callee.object.name === "React";
|
|
30225
|
+
return isReactNamespaceImportReference(namespaceReference);
|
|
30231
30226
|
};
|
|
30232
30227
|
const isHookCallee$1 = (analysis, node, hookName) => {
|
|
30233
30228
|
if (!node) return false;
|
|
30234
30229
|
if (isNodeOfType(node, "Identifier")) {
|
|
30235
30230
|
if (node.name === hookName) return true;
|
|
30236
30231
|
if (isReactNamedImportReference(getRef(analysis, node), hookName)) return true;
|
|
30237
|
-
const
|
|
30238
|
-
|
|
30239
|
-
if (parent && isNodeOfType(parent, "MemberExpression") && parent.object === receiverRoot && isReactNamespaceReceiver(analysis, node) && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
|
|
30232
|
+
const parent = node.parent;
|
|
30233
|
+
if (parent && isNodeOfType(parent, "MemberExpression") && isNodeOfType(parent.object, "Identifier") && parent.object.name === "React" && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
|
|
30240
30234
|
return false;
|
|
30241
30235
|
}
|
|
30242
|
-
if (isNodeOfType(node, "MemberExpression"))
|
|
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
|
+
}
|
|
30243
30240
|
return false;
|
|
30244
30241
|
};
|
|
30245
30242
|
const isUseEffect = (node) => {
|
|
@@ -30663,88 +30660,7 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
|
|
|
30663
30660
|
if (HANDLER_BINDING_NAME_PATTERN.test(bindingName)) return true;
|
|
30664
30661
|
return isSetterWiredToJsxHandler(componentFunction, bindingName);
|
|
30665
30662
|
};
|
|
30666
|
-
const
|
|
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) => {
|
|
30663
|
+
const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
|
|
30748
30664
|
if (!setterRef.resolved) return false;
|
|
30749
30665
|
const componentFunction = findEnclosingFunction$1(effectNode);
|
|
30750
30666
|
if (!componentFunction) return false;
|
|
@@ -30753,11 +30669,6 @@ const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, incl
|
|
|
30753
30669
|
const identifier = reference.identifier;
|
|
30754
30670
|
if (isAstDescendant(identifier, effectNode)) continue;
|
|
30755
30671
|
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;
|
|
30761
30672
|
}
|
|
30762
30673
|
return false;
|
|
30763
30674
|
};
|
|
@@ -31647,7 +31558,7 @@ const areInMutuallyExclusiveBranches = (leftNode, rightNode) => {
|
|
|
31647
31558
|
}
|
|
31648
31559
|
return false;
|
|
31649
31560
|
};
|
|
31650
|
-
const collectEffectStateWriteFacts = (analysis,
|
|
31561
|
+
const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) => {
|
|
31651
31562
|
const frames = collectBoundedEffectExecutionFrames(analysis, effectNode, currentFilename);
|
|
31652
31563
|
if (frames.length === 0) return [];
|
|
31653
31564
|
const effectHasCleanup = hasCleanup(analysis, effectNode);
|
|
@@ -31677,7 +31588,7 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
|
|
|
31677
31588
|
for (const returnedExpression of returnedExpressions) mergeEvidence(valueEvidence, collectValueEvidence(analysis, returnedExpression, updaterFrame, remainingValueCallFrames));
|
|
31678
31589
|
} else valueEvidence = collectValueEvidence(analysis, writtenValue, frame, remainingValueCallFrames);
|
|
31679
31590
|
const sourceReferences = [...valueEvidence.sourceReferences].filter((sourceReference) => getUseStateDecl(analysis, sourceReference) !== stateDeclarator);
|
|
31680
|
-
const hasIndependentWriter = hasUserInputSetterWriter(
|
|
31591
|
+
const hasIndependentWriter = hasUserInputSetterWriter(setterReference, effectNode, true);
|
|
31681
31592
|
const doesMatchStateInitializer = matchesStateInitializer(analysis, callExpression, stateDeclarator);
|
|
31682
31593
|
if (effectHasCleanup && (frame.isDeferred || valueEvidence.hasUnknownSource || valueEvidence.hasDeferredIntroducedValue || valueEvidence.readsExternalValue)) cleanupManagedStateDeclarators.add(stateDeclarator);
|
|
31683
31594
|
const isRenderKnownCopy = sourceReferences.length > 0 && !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue && !hasIndependentWriter;
|
|
@@ -31718,7 +31629,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
31718
31629
|
const dependencyReferences = getEffectDepsRefs(analysis, node);
|
|
31719
31630
|
if (!dependencyReferences) return;
|
|
31720
31631
|
if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
|
|
31721
|
-
for (const fact of collectEffectStateWriteFacts(analysis,
|
|
31632
|
+
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
31722
31633
|
if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
|
|
31723
31634
|
context.report({
|
|
31724
31635
|
node: fact.callExpression,
|
|
@@ -36292,7 +36203,7 @@ const noDerivedState = defineRule({
|
|
|
36292
36203
|
if (!isUseEffect(node)) return;
|
|
36293
36204
|
const analysis = getProgramAnalysis(node);
|
|
36294
36205
|
if (!analysis) return;
|
|
36295
|
-
for (const fact of collectEffectStateWriteFacts(analysis,
|
|
36206
|
+
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
36296
36207
|
if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
|
|
36297
36208
|
reportStateWrite(fact.callExpression, fact.stateDeclarator);
|
|
36298
36209
|
}
|
|
@@ -36312,7 +36223,7 @@ const noDerivedStateEffect = defineRule({
|
|
|
36312
36223
|
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
36313
36224
|
const analysis = getProgramAnalysis(node);
|
|
36314
36225
|
if (!analysis) return;
|
|
36315
|
-
if (!collectEffectStateWriteFacts(analysis,
|
|
36226
|
+
if (!collectEffectStateWriteFacts(analysis, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
|
|
36316
36227
|
context.report({
|
|
36317
36228
|
node,
|
|
36318
36229
|
message: "You pay an extra render for state you can derive from other values."
|
|
@@ -36788,20 +36699,9 @@ const noDidMountSetState = defineRule({
|
|
|
36788
36699
|
}
|
|
36789
36700
|
});
|
|
36790
36701
|
//#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
|
|
36801
36702
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
36802
36703
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
36803
36704
|
const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
36804
|
-
const DIFFERENCE_OPERATORS = new Set(["!=", "!=="]);
|
|
36805
36705
|
const EQUALITY_OPERATORS = new Set([
|
|
36806
36706
|
"==",
|
|
36807
36707
|
"===",
|
|
@@ -36813,8 +36713,6 @@ const FUNCTION_NODE_TYPES = new Set([
|
|
|
36813
36713
|
"FunctionExpression",
|
|
36814
36714
|
"ArrowFunctionExpression"
|
|
36815
36715
|
]);
|
|
36816
|
-
const CLASS_NODE_TYPES = new Set(["ClassDeclaration", "ClassExpression"]);
|
|
36817
|
-
const callbackRefFieldNamesByClass = /* @__PURE__ */ new WeakMap();
|
|
36818
36716
|
const isLifecycleMethodFunction = (node) => {
|
|
36819
36717
|
if (!FUNCTION_NODE_TYPES.has(node.type)) return false;
|
|
36820
36718
|
const parent = node.parent;
|
|
@@ -36870,187 +36768,6 @@ const getStaticMemberName = (node) => {
|
|
|
36870
36768
|
if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
|
|
36871
36769
|
return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
|
|
36872
36770
|
};
|
|
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
|
-
};
|
|
37054
36771
|
const getThisStateFieldName = (node) => {
|
|
37055
36772
|
const unwrappedNode = stripParenExpression(node);
|
|
37056
36773
|
if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
|
|
@@ -37068,17 +36785,15 @@ const collectLocalInitializers = (lifecycleFunction) => {
|
|
|
37068
36785
|
});
|
|
37069
36786
|
return initializers;
|
|
37070
36787
|
};
|
|
37071
|
-
const derivesFromPostMountValue = (node, localInitializers,
|
|
36788
|
+
const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
|
|
37072
36789
|
if (readsPostMountValue(node)) return true;
|
|
37073
|
-
const fieldName = getThisFieldName(node);
|
|
37074
|
-
if (fieldName && callbackRefFieldNames.has(fieldName)) return true;
|
|
37075
36790
|
const referencedNames = /* @__PURE__ */ new Set();
|
|
37076
36791
|
collectReferenceIdentifierNames(node, referencedNames);
|
|
37077
36792
|
for (const referencedName of referencedNames) {
|
|
37078
36793
|
if (visitedNames.has(referencedName)) continue;
|
|
37079
36794
|
const initializer = localInitializers.get(referencedName);
|
|
37080
36795
|
if (!initializer) continue;
|
|
37081
|
-
if (derivesFromPostMountValue(initializer, localInitializers,
|
|
36796
|
+
if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
|
|
37082
36797
|
}
|
|
37083
36798
|
return false;
|
|
37084
36799
|
};
|
|
@@ -37092,84 +36807,50 @@ const getSetStateFieldValue = (setStateCall, fieldName) => {
|
|
|
37092
36807
|
}
|
|
37093
36808
|
return null;
|
|
37094
36809
|
};
|
|
37095
|
-
const isConvergentPostMountGuard = (test, setStateCall, localInitializers
|
|
37096
|
-
|
|
37097
|
-
|
|
37098
|
-
if (
|
|
37099
|
-
|
|
37100
|
-
const
|
|
37101
|
-
|
|
37102
|
-
|
|
37103
|
-
|
|
37104
|
-
|
|
37105
|
-
|
|
37106
|
-
|
|
37107
|
-
|
|
37108
|
-
|
|
37109
|
-
|
|
37110
|
-
|
|
37111
|
-
return
|
|
37112
|
-
};
|
|
37113
|
-
const
|
|
37114
|
-
|
|
37115
|
-
|
|
37116
|
-
|
|
37117
|
-
|
|
37118
|
-
|
|
37119
|
-
|
|
37120
|
-
|
|
37121
|
-
|
|
37122
|
-
|
|
37123
|
-
|
|
37124
|
-
|
|
37125
|
-
|
|
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));
|
|
36810
|
+
const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
|
|
36811
|
+
let qualifies = false;
|
|
36812
|
+
walkAst(test, (node) => {
|
|
36813
|
+
if (qualifies) return false;
|
|
36814
|
+
if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
|
|
36815
|
+
const leftFieldName = getThisStateFieldName(node.left);
|
|
36816
|
+
const rightFieldName = getThisStateFieldName(node.right);
|
|
36817
|
+
const fieldName = leftFieldName ?? rightFieldName;
|
|
36818
|
+
const comparedValue = leftFieldName ? node.right : node.left;
|
|
36819
|
+
if (!fieldName || !leftFieldName && !rightFieldName) return;
|
|
36820
|
+
const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
|
|
36821
|
+
if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
|
|
36822
|
+
if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
|
|
36823
|
+
qualifies = true;
|
|
36824
|
+
return false;
|
|
36825
|
+
});
|
|
36826
|
+
return qualifies;
|
|
36827
|
+
};
|
|
36828
|
+
const isDiffGuardTest = (test, paramNames, derivedNames) => {
|
|
36829
|
+
if (referencesAnyName(test, paramNames)) return true;
|
|
36830
|
+
let qualifies = false;
|
|
36831
|
+
walkAst(test, (node) => {
|
|
36832
|
+
if (qualifies) return false;
|
|
36833
|
+
if (!isNodeOfType(node, "BinaryExpression")) return;
|
|
36834
|
+
if (!EQUALITY_OPERATORS.has(node.operator)) return;
|
|
36835
|
+
if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames) && (referencesAnyName(node.left, derivedNames) || referencesAnyName(node.right, derivedNames))) {
|
|
36836
|
+
qualifies = true;
|
|
36837
|
+
return false;
|
|
36838
|
+
}
|
|
36839
|
+
});
|
|
36840
|
+
return qualifies;
|
|
37139
36841
|
};
|
|
37140
|
-
const isInsideDiffGuard = (setStateCall
|
|
36842
|
+
const isInsideDiffGuard = (setStateCall) => {
|
|
37141
36843
|
const lifecycleFunction = findEnclosingLifecycleFunction(setStateCall);
|
|
37142
36844
|
if (!lifecycleFunction) return false;
|
|
37143
36845
|
const paramNames = /* @__PURE__ */ new Set();
|
|
37144
|
-
const
|
|
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);
|
|
36846
|
+
for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
|
|
37150
36847
|
const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
|
|
37151
36848
|
const localInitializers = collectLocalInitializers(lifecycleFunction);
|
|
37152
|
-
const lifecycleWrittenFieldNames = collectLifecycleWrittenFieldNames(lifecycleFunction);
|
|
37153
|
-
const callbackRefFieldNames = new Set([...getCallbackRefFieldNames(findEnclosingClass(lifecycleFunction), scopes)].filter((fieldName) => !lifecycleWrittenFieldNames.has(fieldName)));
|
|
37154
36849
|
let child = setStateCall;
|
|
37155
36850
|
let ancestor = setStateCall.parent;
|
|
37156
36851
|
while (ancestor && ancestor !== lifecycleFunction) {
|
|
37157
|
-
|
|
37158
|
-
|
|
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;
|
|
36852
|
+
const guardTest = isNodeOfType(ancestor, "IfStatement") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "ConditionalExpression") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "LogicalExpression") && ancestor.operator === "&&" && child === ancestor.right && ancestor.left || null;
|
|
36853
|
+
if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers))) return true;
|
|
37173
36854
|
child = ancestor;
|
|
37174
36855
|
ancestor = ancestor.parent ?? null;
|
|
37175
36856
|
}
|
|
@@ -37191,7 +36872,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
37191
36872
|
if (!isNodeOfType(stripParenExpression(node.callee.object), "ThisExpression")) return;
|
|
37192
36873
|
if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
|
|
37193
36874
|
if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
37194
|
-
if (isInsideDiffGuard(node
|
|
36875
|
+
if (isInsideDiffGuard(node)) return;
|
|
37195
36876
|
context.report({
|
|
37196
36877
|
node: node.callee,
|
|
37197
36878
|
message: MESSAGE$27
|
|
@@ -40913,223 +40594,41 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
|
|
|
40913
40594
|
if (leftResult === false && rightResult === false) return false;
|
|
40914
40595
|
return null;
|
|
40915
40596
|
};
|
|
40916
|
-
const readHydrationConditionResult = (expression, context, runtime
|
|
40597
|
+
const readHydrationConditionResult = (expression, context, runtime) => {
|
|
40917
40598
|
const unwrappedExpression = stripParenExpression(expression);
|
|
40918
40599
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
40919
40600
|
if (predicateMatch) return predicateMatch[`${runtime}Result`];
|
|
40920
40601
|
const staticResult = readInitialStateBoolean(unwrappedExpression, context.scopes);
|
|
40921
40602
|
if (staticResult !== null) return staticResult;
|
|
40922
|
-
const expressionSymbol = isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression) : null;
|
|
40923
|
-
const parameterValue = expressionSymbol ? state.parameterValuesBySymbolId.get(expressionSymbol.id) : null;
|
|
40924
|
-
if (expressionSymbol && parameterValue && !state.visitedSymbolIds.has(expressionSymbol.id)) {
|
|
40925
|
-
state.visitedSymbolIds.add(expressionSymbol.id);
|
|
40926
|
-
const result = readHydrationConditionResult(parameterValue, context, runtime, state);
|
|
40927
|
-
state.visitedSymbolIds.delete(expressionSymbol.id);
|
|
40928
|
-
return result;
|
|
40929
|
-
}
|
|
40930
|
-
if (expressionSymbol && expressionSymbol.kind === "const" && expressionSymbol.initializer && expressionSymbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(expressionSymbol.id)) {
|
|
40931
|
-
state.visitedSymbolIds.add(expressionSymbol.id);
|
|
40932
|
-
const result = readHydrationConditionResult(expressionSymbol.initializer, context, runtime, state);
|
|
40933
|
-
state.visitedSymbolIds.delete(expressionSymbol.id);
|
|
40934
|
-
return result;
|
|
40935
|
-
}
|
|
40936
|
-
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
40937
|
-
const callArguments = unwrappedExpression.arguments ?? [];
|
|
40938
|
-
if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
|
|
40939
|
-
allowGlobalReactNamespace: true,
|
|
40940
|
-
resolveNamedAliases: true
|
|
40941
|
-
})) {
|
|
40942
|
-
const callbackArgument = callArguments[0];
|
|
40943
|
-
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
|
|
40944
|
-
const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
|
|
40945
|
-
return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? readHydrationFunctionResult(callbackFunction, context, runtime, state) : null;
|
|
40946
|
-
}
|
|
40947
|
-
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
40948
|
-
if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return readHydrationConditionResult(callArguments[0], context, runtime, state);
|
|
40949
|
-
const helperFunction = resolveExactLocalFunction(callee, context.scopes);
|
|
40950
|
-
if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
|
|
40951
|
-
const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
|
|
40952
|
-
for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
|
|
40953
|
-
const parameter = helperFunction.params[parameterIndex];
|
|
40954
|
-
const argument = callArguments[parameterIndex];
|
|
40955
|
-
if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
|
|
40956
|
-
const parameterSymbol = context.scopes.symbolFor(parameter);
|
|
40957
|
-
if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
|
|
40958
|
-
}
|
|
40959
|
-
return readHydrationFunctionResult(helperFunction, context, runtime, {
|
|
40960
|
-
...state,
|
|
40961
|
-
parameterValuesBySymbolId
|
|
40962
|
-
});
|
|
40963
|
-
}
|
|
40964
40603
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
|
|
40965
|
-
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime
|
|
40604
|
+
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime);
|
|
40966
40605
|
return argumentResult === null ? null : !argumentResult;
|
|
40967
40606
|
}
|
|
40968
40607
|
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
40969
|
-
return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime
|
|
40970
|
-
};
|
|
40971
|
-
const readHydrationStatementResult = (statement, context, runtime, state) => {
|
|
40972
|
-
if (isNodeOfType(statement, "ReturnStatement")) return {
|
|
40973
|
-
didReturn: true,
|
|
40974
|
-
value: statement.argument ? readHydrationConditionResult(statement.argument, context, runtime, state) : null
|
|
40975
|
-
};
|
|
40976
|
-
if (isNodeOfType(statement, "BlockStatement")) {
|
|
40977
|
-
for (const childStatement of statement.body) {
|
|
40978
|
-
const result = readHydrationStatementResult(childStatement, context, runtime, state);
|
|
40979
|
-
if (result.didReturn) return result;
|
|
40980
|
-
if (statementAlwaysExits(childStatement)) break;
|
|
40981
|
-
}
|
|
40982
|
-
return {
|
|
40983
|
-
didReturn: false,
|
|
40984
|
-
value: null
|
|
40985
|
-
};
|
|
40986
|
-
}
|
|
40987
|
-
if (!isNodeOfType(statement, "IfStatement")) return {
|
|
40988
|
-
didReturn: false,
|
|
40989
|
-
value: null
|
|
40990
|
-
};
|
|
40991
|
-
const conditionResult = readHydrationConditionResult(statement.test, context, runtime, state);
|
|
40992
|
-
if (conditionResult !== null) {
|
|
40993
|
-
const selectedBranch = conditionResult ? statement.consequent : statement.alternate;
|
|
40994
|
-
return selectedBranch ? readHydrationStatementResult(selectedBranch, context, runtime, state) : {
|
|
40995
|
-
didReturn: false,
|
|
40996
|
-
value: null
|
|
40997
|
-
};
|
|
40998
|
-
}
|
|
40999
|
-
const consequentResult = readHydrationStatementResult(statement.consequent, context, runtime, state);
|
|
41000
|
-
const alternateResult = statement.alternate ? readHydrationStatementResult(statement.alternate, context, runtime, state) : {
|
|
41001
|
-
didReturn: false,
|
|
41002
|
-
value: null
|
|
41003
|
-
};
|
|
41004
|
-
return consequentResult.didReturn && alternateResult.didReturn && consequentResult.value !== null && consequentResult.value === alternateResult.value ? consequentResult : {
|
|
41005
|
-
didReturn: consequentResult.didReturn || alternateResult.didReturn,
|
|
41006
|
-
value: null
|
|
41007
|
-
};
|
|
41008
|
-
};
|
|
41009
|
-
const readHydrationFunctionResult = (functionNode, context, runtime, state) => {
|
|
41010
|
-
if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
|
|
41011
|
-
state.visitedFunctionNodes.add(functionNode);
|
|
41012
|
-
const result = isNodeOfType(functionNode.body, "BlockStatement") ? readHydrationStatementResult(functionNode.body, context, runtime, state).value : readHydrationConditionResult(functionNode.body, context, runtime, state);
|
|
41013
|
-
state.visitedFunctionNodes.delete(functionNode);
|
|
41014
|
-
return result;
|
|
41015
|
-
};
|
|
41016
|
-
const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, scopes) => {
|
|
41017
|
-
const left = stripParenExpression(leftExpression);
|
|
41018
|
-
const right = stripParenExpression(rightExpression);
|
|
41019
|
-
if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
|
|
41020
|
-
const leftSymbol = scopes.symbolFor(left);
|
|
41021
|
-
const rightSymbol = scopes.symbolFor(right);
|
|
41022
|
-
return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
|
|
41023
|
-
}
|
|
41024
|
-
if (isNodeOfType(left, "MemberExpression") && isNodeOfType(right, "MemberExpression")) return doEquivalentExpressionBindingsMatch(left.object, right.object, scopes) && (!left.computed || doEquivalentExpressionBindingsMatch(left.property, right.property, scopes));
|
|
41025
|
-
if (isNodeOfType(left, "CallExpression") && isNodeOfType(right, "CallExpression")) {
|
|
41026
|
-
const rightArguments = right.arguments ?? [];
|
|
41027
|
-
return doEquivalentExpressionBindingsMatch(left.callee, right.callee, scopes) && (left.arguments ?? []).every((argument, index) => {
|
|
41028
|
-
const rightArgument = rightArguments[index];
|
|
41029
|
-
return Boolean(rightArgument && doEquivalentExpressionBindingsMatch(argument, rightArgument, scopes));
|
|
41030
|
-
});
|
|
41031
|
-
}
|
|
41032
|
-
return true;
|
|
40608
|
+
return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime), readHydrationConditionResult(unwrappedExpression.right, context, runtime));
|
|
41033
40609
|
};
|
|
41034
|
-
const
|
|
41035
|
-
if (areExpressionsStructurallyEqual(leftValue, rightValue)) return doEquivalentExpressionBindingsMatch(leftValue, rightValue, context.scopes);
|
|
41036
|
-
const leftBoolean = readInitialStateBoolean(leftValue, context.scopes);
|
|
41037
|
-
const rightBoolean = readInitialStateBoolean(rightValue, context.scopes);
|
|
41038
|
-
return leftBoolean !== null && rightBoolean !== null && leftBoolean === rightBoolean;
|
|
41039
|
-
};
|
|
41040
|
-
const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
|
|
41041
|
-
const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
|
|
41042
|
-
return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
|
|
41043
|
-
};
|
|
41044
|
-
const matchHydrationConditionInternal = (expression, context, state) => {
|
|
40610
|
+
const matchHydrationCondition = (expression, context) => {
|
|
41045
40611
|
const unwrappedExpression = stripParenExpression(expression);
|
|
41046
40612
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
41047
40613
|
if (predicateMatch) return {
|
|
41048
40614
|
predicateMatch,
|
|
41049
40615
|
predicateNode: unwrappedExpression
|
|
41050
40616
|
};
|
|
41051
|
-
if (isNodeOfType(unwrappedExpression, "
|
|
41052
|
-
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
41053
|
-
const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
|
|
41054
|
-
if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
|
|
41055
|
-
state.visitedSymbolIds.add(symbol.id);
|
|
41056
|
-
const match = matchHydrationConditionInternal(parameterValue, context, state);
|
|
41057
|
-
state.visitedSymbolIds.delete(symbol.id);
|
|
41058
|
-
return match;
|
|
41059
|
-
}
|
|
41060
|
-
if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
|
|
41061
|
-
state.visitedSymbolIds.add(symbol.id);
|
|
41062
|
-
const match = matchHydrationConditionInternal(symbol.initializer, context, state);
|
|
41063
|
-
state.visitedSymbolIds.delete(symbol.id);
|
|
41064
|
-
return match;
|
|
41065
|
-
}
|
|
41066
|
-
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
41067
|
-
const callArguments = unwrappedExpression.arguments ?? [];
|
|
41068
|
-
if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
|
|
41069
|
-
allowGlobalReactNamespace: true,
|
|
41070
|
-
resolveNamedAliases: true
|
|
41071
|
-
})) {
|
|
41072
|
-
const callbackArgument = callArguments[0];
|
|
41073
|
-
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
|
|
41074
|
-
const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
|
|
41075
|
-
return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionResult(callbackFunction, context, state) : null;
|
|
41076
|
-
}
|
|
41077
|
-
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
41078
|
-
if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return matchHydrationConditionInternal(callArguments[0], context, state);
|
|
41079
|
-
const helperFunction = resolveExactLocalFunction(callee, context.scopes);
|
|
41080
|
-
if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
|
|
41081
|
-
const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
|
|
41082
|
-
for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
|
|
41083
|
-
const parameter = helperFunction.params[parameterIndex];
|
|
41084
|
-
const argument = callArguments[parameterIndex];
|
|
41085
|
-
if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
|
|
41086
|
-
const parameterSymbol = context.scopes.symbolFor(parameter);
|
|
41087
|
-
if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
|
|
41088
|
-
}
|
|
41089
|
-
return matchHydrationFunctionResult(helperFunction, context, {
|
|
41090
|
-
...state,
|
|
41091
|
-
parameterValuesBySymbolId
|
|
41092
|
-
});
|
|
41093
|
-
}
|
|
41094
|
-
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
|
|
40617
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationCondition(unwrappedExpression.argument, context);
|
|
41095
40618
|
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
41096
|
-
const leftMatch =
|
|
41097
|
-
const rightMatch =
|
|
40619
|
+
const leftMatch = matchHydrationCondition(unwrappedExpression.left, context);
|
|
40620
|
+
const rightMatch = matchHydrationCondition(unwrappedExpression.right, context);
|
|
40621
|
+
if (leftMatch && rightMatch) {
|
|
40622
|
+
const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client");
|
|
40623
|
+
const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server");
|
|
40624
|
+
return clientResult !== null && serverResult !== null && clientResult !== serverResult ? leftMatch : null;
|
|
40625
|
+
}
|
|
41098
40626
|
const nestedMatch = leftMatch ?? rightMatch;
|
|
41099
40627
|
if (!nestedMatch) return null;
|
|
41100
|
-
const
|
|
41101
|
-
|
|
41102
|
-
return
|
|
41103
|
-
};
|
|
41104
|
-
const matchHydrationReturningStatement = (statement, context, state) => {
|
|
41105
|
-
if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? matchHydrationConditionInternal(statement.argument, context, state) : null;
|
|
41106
|
-
if (isNodeOfType(statement, "IfStatement")) {
|
|
41107
|
-
const conditionMatch = matchHydrationConditionInternal(statement.test, context, state);
|
|
41108
|
-
const consequentValues = getReturnedValues(statement.consequent);
|
|
41109
|
-
const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
|
|
41110
|
-
if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
|
|
41111
|
-
return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
|
|
41112
|
-
}
|
|
41113
|
-
if (!isNodeOfType(statement, "BlockStatement")) return null;
|
|
41114
|
-
for (const childStatement of statement.body) {
|
|
41115
|
-
const match = matchHydrationReturningStatement(childStatement, context, state);
|
|
41116
|
-
if (match) return match;
|
|
41117
|
-
if (statementAlwaysExits(childStatement)) break;
|
|
41118
|
-
}
|
|
41119
|
-
return null;
|
|
40628
|
+
const otherResult = readInitialStateBoolean(leftMatch ? unwrappedExpression.right : unwrappedExpression.left, context.scopes);
|
|
40629
|
+
if (unwrappedExpression.operator === "&&" && otherResult === false || unwrappedExpression.operator === "||" && otherResult === true) return null;
|
|
40630
|
+
return nestedMatch;
|
|
41120
40631
|
};
|
|
41121
|
-
const matchHydrationFunctionResult = (functionNode, context, state) => {
|
|
41122
|
-
if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
|
|
41123
|
-
state.visitedFunctionNodes.add(functionNode);
|
|
41124
|
-
const match = isNodeOfType(functionNode.body, "BlockStatement") ? matchHydrationReturningStatement(functionNode.body, context, state) : matchHydrationConditionInternal(functionNode.body, context, state);
|
|
41125
|
-
state.visitedFunctionNodes.delete(functionNode);
|
|
41126
|
-
return match;
|
|
41127
|
-
};
|
|
41128
|
-
const matchHydrationCondition = (expression, context) => matchHydrationConditionInternal(expression, context, {
|
|
41129
|
-
parameterValuesBySymbolId: /* @__PURE__ */ new Map(),
|
|
41130
|
-
visitedFunctionNodes: /* @__PURE__ */ new Set(),
|
|
41131
|
-
visitedSymbolIds: /* @__PURE__ */ new Set()
|
|
41132
|
-
});
|
|
41133
40632
|
const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
|
|
41134
40633
|
const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
|
|
41135
40634
|
if (!leftNode || !rightNode) return leftNode === rightNode;
|
|
@@ -41272,17 +40771,17 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
41272
40771
|
const { predicateMatch, predicateNode } = conditionMatch;
|
|
41273
40772
|
if (reportedNodes.has(predicateNode)) return;
|
|
41274
40773
|
if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
|
|
41275
|
-
const componentOrHookNode = findRenderPhaseComponentOrHook(
|
|
40774
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(predicateNode, context.scopes);
|
|
41276
40775
|
if (!componentOrHookNode) return;
|
|
41277
40776
|
if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
|
|
41278
|
-
if (requiresRenderedContext && !isInRenderedOutput(
|
|
40777
|
+
if (requiresRenderedContext && !isInRenderedOutput(predicateNode, componentOrHookNode, context.scopes)) return;
|
|
41279
40778
|
if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
|
|
41280
|
-
const attribute = findEnclosingJsxAttribute(
|
|
40779
|
+
const attribute = findEnclosingJsxAttribute(predicateNode);
|
|
41281
40780
|
if (!attribute || isEventHandlerAttribute(attribute)) return;
|
|
41282
40781
|
}
|
|
41283
|
-
if (fileIsEmailTemplate || isGatedByFalsyInitialState(
|
|
41284
|
-
if (isAfterClientOnlyEarlyReturn(
|
|
41285
|
-
const openingElement = findEnclosingJsxOpeningElement(
|
|
40782
|
+
if (fileIsEmailTemplate || isGatedByFalsyInitialState(predicateNode, context.scopes)) return;
|
|
40783
|
+
if (isAfterClientOnlyEarlyReturn(predicateNode, componentOrHookNode, context.scopes)) return;
|
|
40784
|
+
const openingElement = findEnclosingJsxOpeningElement(predicateNode);
|
|
41286
40785
|
if (hasSuppressHydrationWarningAttribute(openingElement) && !isStructuralRenderedValue(leftBranch) && !isStructuralRenderedValue(rightBranch)) return;
|
|
41287
40786
|
if (branchRootsSuppressSameElement(leftBranch, rightBranch)) return;
|
|
41288
40787
|
if (isGeneratedImageRenderContext(context, openingElement ?? leftBranch)) return;
|
|
@@ -41664,7 +41163,7 @@ const noInitializeState = defineRule({
|
|
|
41664
41163
|
if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
|
|
41665
41164
|
const analysis = getProgramAnalysis(node);
|
|
41666
41165
|
if (!analysis) return;
|
|
41667
|
-
for (const fact of collectEffectStateWriteFacts(analysis,
|
|
41166
|
+
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
41668
41167
|
if (!fact.isRenderKnownCopy || fact.matchesStateInitializer || fact.resetsSourceState) continue;
|
|
41669
41168
|
const stateName = getStateName(fact.stateDeclarator);
|
|
41670
41169
|
context.report({
|
|
@@ -57229,39 +56728,8 @@ const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node,
|
|
|
57229
56728
|
destructureIndex: 1
|
|
57230
56729
|
});
|
|
57231
56730
|
//#endregion
|
|
57232
|
-
//#region src/plugin/utils/unwrap-return-expression.ts
|
|
57233
|
-
const unwrapReturnExpression = (node) => isNodeOfType(node, "ReturnStatement") && node.argument ? node.argument : node;
|
|
57234
|
-
//#endregion
|
|
57235
56731
|
//#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
|
|
57236
56732
|
const USE_EFFECT_ONLY = new Set(["useEffect"]);
|
|
57237
|
-
const USE_CALLBACK_ONLY = new Set(["useCallback"]);
|
|
57238
|
-
const USE_STATE_ONLY = new Set(["useState"]);
|
|
57239
|
-
const REACT_API_CALL_OPTIONS = {
|
|
57240
|
-
allowGlobalReactNamespace: true,
|
|
57241
|
-
allowUnboundBareCalls: true,
|
|
57242
|
-
resolveNamedAliases: true
|
|
57243
|
-
};
|
|
57244
|
-
const expressionReadsDerivedSymbol = (context, expression, stateDerivedSymbolIds) => {
|
|
57245
|
-
let readsDerivedSymbol = false;
|
|
57246
|
-
walkAst(expression, (node) => {
|
|
57247
|
-
if (readsDerivedSymbol) return false;
|
|
57248
|
-
if (node !== expression && isFunctionLike$1(node)) return false;
|
|
57249
|
-
if (isNodeOfType(node, "Identifier") && stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) readsDerivedSymbol = true;
|
|
57250
|
-
});
|
|
57251
|
-
return readsDerivedSymbol;
|
|
57252
|
-
};
|
|
57253
|
-
const getStaticObjectPropertyName = (property) => {
|
|
57254
|
-
if (!isNodeOfType(property, "Property") || property.computed || property.method || property.kind !== "init") return null;
|
|
57255
|
-
if (isNodeOfType(property.key, "Identifier")) return property.key.name;
|
|
57256
|
-
if (isNodeOfType(property.key, "Literal") && (typeof property.key.value === "string" || typeof property.key.value === "number")) return String(property.key.value);
|
|
57257
|
-
return null;
|
|
57258
|
-
};
|
|
57259
|
-
const isNonVisibleJsxSpreadProperty = (propertyName) => propertyName === "id" || propertyName.startsWith("aria-") || /^on[A-Z]/.test(propertyName);
|
|
57260
|
-
const isTransparentAssignmentTarget = (identifier) => {
|
|
57261
|
-
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
57262
|
-
const parent = expressionRoot.parent;
|
|
57263
|
-
return Boolean(isNodeOfType(parent, "AssignmentExpression") && parent.left === expressionRoot || isNodeOfType(parent, "UpdateExpression") && parent.argument === expressionRoot || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expressionRoot);
|
|
57264
|
-
};
|
|
57265
56733
|
const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
|
|
57266
56734
|
let readsCurrent = false;
|
|
57267
56735
|
walkAst(argument, (child) => {
|
|
@@ -57313,166 +56781,6 @@ const isStateUsedOnlyInIdOrAriaAttributes = (setterCall, setterName) => {
|
|
|
57313
56781
|
});
|
|
57314
56782
|
return referenceCount > 0 && !nonAriaReferenceFound;
|
|
57315
56783
|
};
|
|
57316
|
-
const isGlobalWindowMember = (context, node, propertyName) => {
|
|
57317
|
-
const member = stripParenExpression(node);
|
|
57318
|
-
if (!isNodeOfType(member, "MemberExpression") || member.computed) return false;
|
|
57319
|
-
const receiver = stripParenExpression(member.object);
|
|
57320
|
-
return isNodeOfType(receiver, "Identifier") && receiver.name === "window" && context.scopes.isGlobalReference(receiver) && isNodeOfType(member.property, "Identifier") && member.property.name === propertyName;
|
|
57321
|
-
};
|
|
57322
|
-
const getDirectWindowWidthSetter = (context, statement) => {
|
|
57323
|
-
const call = unwrapDiscardedExpression(statement);
|
|
57324
|
-
if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 1) return null;
|
|
57325
|
-
if (!isNodeOfType(call.callee, "Identifier") || !isSetterCall(call)) return null;
|
|
57326
|
-
const argument = call.arguments[0];
|
|
57327
|
-
return isGlobalWindowMember(context, argument, "innerWidth") ? call : null;
|
|
57328
|
-
};
|
|
57329
|
-
const getResizeListenerHandler = (context, statement, methodName) => {
|
|
57330
|
-
const call = unwrapDiscardedExpression(statement);
|
|
57331
|
-
if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 2) return null;
|
|
57332
|
-
if (!isGlobalWindowMember(context, call.callee, methodName)) return null;
|
|
57333
|
-
const eventName = call.arguments[0];
|
|
57334
|
-
const handler = call.arguments[1];
|
|
57335
|
-
if (!isNodeOfType(eventName, "Literal") || eventName.value !== "resize") return null;
|
|
57336
|
-
return isNodeOfType(handler, "Identifier") ? handler : null;
|
|
57337
|
-
};
|
|
57338
|
-
const getCleanupResizeHandler = (context, statement) => {
|
|
57339
|
-
if (!isNodeOfType(statement, "ReturnStatement") || !isFunctionLike$1(statement.argument)) return null;
|
|
57340
|
-
const cleanupStatements = getCallbackStatements(statement.argument);
|
|
57341
|
-
if (cleanupStatements.length !== 1) return null;
|
|
57342
|
-
return getResizeListenerHandler(context, unwrapReturnExpression(cleanupStatements[0]), "removeEventListener");
|
|
57343
|
-
};
|
|
57344
|
-
const findExactViewportState = (context, componentFunction, setterCall) => {
|
|
57345
|
-
if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
|
|
57346
|
-
const componentBody = componentFunction.body;
|
|
57347
|
-
if (!isNodeOfType(setterCall.callee, "Identifier")) return null;
|
|
57348
|
-
const setterSymbol = context.scopes.symbolFor(setterCall.callee);
|
|
57349
|
-
if (!setterSymbol || setterSymbol.kind !== "const" || !isNodeOfType(setterSymbol.declarationNode, "VariableDeclarator")) return null;
|
|
57350
|
-
const declarator = setterSymbol.declarationNode;
|
|
57351
|
-
if (!isNodeOfType(declarator.id, "ArrayPattern")) return null;
|
|
57352
|
-
const stateIdentifier = declarator.id.elements?.[0];
|
|
57353
|
-
const setterIdentifier = declarator.id.elements?.[1];
|
|
57354
|
-
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;
|
|
57355
|
-
const initializer = declarator.init.arguments?.[0];
|
|
57356
|
-
if (!isNodeOfType(initializer, "Literal") || initializer.value !== 0) return null;
|
|
57357
|
-
const stateSymbol = context.scopes.symbolFor(stateIdentifier);
|
|
57358
|
-
if (!stateSymbol) return null;
|
|
57359
|
-
const stateDerivedSymbolIds = new Set([stateSymbol.id]);
|
|
57360
|
-
let didAddDerivedSymbol = true;
|
|
57361
|
-
while (didAddDerivedSymbol) {
|
|
57362
|
-
didAddDerivedSymbol = false;
|
|
57363
|
-
for (const statement of componentBody.body ?? []) {
|
|
57364
|
-
if (!isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
57365
|
-
for (const candidateDeclarator of statement.declarations ?? []) {
|
|
57366
|
-
if (!isNodeOfType(candidateDeclarator.id, "Identifier") || !candidateDeclarator.init) continue;
|
|
57367
|
-
const candidateInitializer = stripParenExpression(candidateDeclarator.init);
|
|
57368
|
-
if (isFunctionLike$1(candidateInitializer) || isNodeOfType(candidateInitializer, "CallExpression") && isReactApiCall(candidateInitializer, USE_CALLBACK_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) continue;
|
|
57369
|
-
if (!expressionReadsDerivedSymbol(context, candidateInitializer, stateDerivedSymbolIds)) continue;
|
|
57370
|
-
const candidateSymbol = context.scopes.symbolFor(candidateDeclarator.id);
|
|
57371
|
-
if (candidateSymbol?.kind === "const" && candidateSymbol.references.every((reference) => reference.flag === "read" && !isTransparentAssignmentTarget(reference.identifier)) && !stateDerivedSymbolIds.has(candidateSymbol.id)) {
|
|
57372
|
-
stateDerivedSymbolIds.add(candidateSymbol.id);
|
|
57373
|
-
didAddDerivedSymbol = true;
|
|
57374
|
-
}
|
|
57375
|
-
}
|
|
57376
|
-
}
|
|
57377
|
-
}
|
|
57378
|
-
const staticSpreadVisibilityBySymbolId = /* @__PURE__ */ new Map();
|
|
57379
|
-
const hasOnlyStaticObjectReferences = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
57380
|
-
const symbol = context.scopes.symbolFor(identifier);
|
|
57381
|
-
if (!symbol) return false;
|
|
57382
|
-
if (visitedSymbolIds.has(symbol.id)) return true;
|
|
57383
|
-
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
57384
|
-
nextVisitedSymbolIds.add(symbol.id);
|
|
57385
|
-
let hasUnknownReference = false;
|
|
57386
|
-
walkAst(componentBody, (node) => {
|
|
57387
|
-
if (hasUnknownReference || !isNodeOfType(node, "Identifier") || context.scopes.symbolFor(node)?.id !== symbol.id || node === symbol.bindingIdentifier) return;
|
|
57388
|
-
const referenceRoot = findTransparentExpressionRoot(node);
|
|
57389
|
-
const parent = referenceRoot.parent;
|
|
57390
|
-
if (isNodeOfType(parent, "JSXSpreadAttribute") && parent.argument === referenceRoot) return;
|
|
57391
|
-
if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier") && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const" && hasOnlyStaticObjectReferences(parent.id, nextVisitedSymbolIds)) return;
|
|
57392
|
-
hasUnknownReference = true;
|
|
57393
|
-
return false;
|
|
57394
|
-
});
|
|
57395
|
-
return !hasUnknownReference;
|
|
57396
|
-
};
|
|
57397
|
-
const classifyStaticSpreadObject = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
57398
|
-
const symbol = context.scopes.symbolFor(identifier);
|
|
57399
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return "unknown";
|
|
57400
|
-
const cachedVisibility = staticSpreadVisibilityBySymbolId.get(symbol.id);
|
|
57401
|
-
if (cachedVisibility) return cachedVisibility;
|
|
57402
|
-
if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || symbol.declarationNode.id !== symbol.bindingIdentifier || !symbol.declarationNode.init) return "unknown";
|
|
57403
|
-
if (!hasOnlyStaticObjectReferences(identifier)) return "unknown";
|
|
57404
|
-
const initializer = stripParenExpression(symbol.declarationNode.init);
|
|
57405
|
-
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
57406
|
-
nextVisitedSymbolIds.add(symbol.id);
|
|
57407
|
-
if (isNodeOfType(initializer, "Identifier")) {
|
|
57408
|
-
const visibility = classifyStaticSpreadObject(initializer, nextVisitedSymbolIds);
|
|
57409
|
-
staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
|
|
57410
|
-
return visibility;
|
|
57411
|
-
}
|
|
57412
|
-
if (!isNodeOfType(initializer, "ObjectExpression")) return "unknown";
|
|
57413
|
-
let visibility = "non-visible";
|
|
57414
|
-
for (const property of initializer.properties ?? []) {
|
|
57415
|
-
const propertyName = getStaticObjectPropertyName(property);
|
|
57416
|
-
if (!isNodeOfType(property, "Property") || !propertyName) {
|
|
57417
|
-
visibility = "unknown";
|
|
57418
|
-
break;
|
|
57419
|
-
}
|
|
57420
|
-
if (expressionReadsDerivedSymbol(context, property.value, stateDerivedSymbolIds) && !isNonVisibleJsxSpreadProperty(propertyName)) visibility = "visible";
|
|
57421
|
-
}
|
|
57422
|
-
staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
|
|
57423
|
-
return visibility;
|
|
57424
|
-
};
|
|
57425
|
-
let hasNonAriaReference = false;
|
|
57426
|
-
walkAst(componentBody, (node) => {
|
|
57427
|
-
if (hasNonAriaReference) return false;
|
|
57428
|
-
if (!isNodeOfType(node, "Identifier") || !stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) return;
|
|
57429
|
-
if (findEnclosingFunction$1(node) !== componentFunction) return;
|
|
57430
|
-
const parent = node.parent;
|
|
57431
|
-
if (parent && (isNodeOfType(parent, "MemberExpression") && parent.property === node && !parent.computed || isNodeOfType(parent, "Property") && parent.key === node && !parent.computed)) return;
|
|
57432
|
-
let cursor = parent;
|
|
57433
|
-
while (cursor && cursor !== componentBody) {
|
|
57434
|
-
if (isFunctionLike$1(cursor)) return;
|
|
57435
|
-
if (isNodeOfType(cursor, "JSXSpreadAttribute")) {
|
|
57436
|
-
if (isNodeOfType(node, "Identifier") && classifyStaticSpreadObject(node) === "visible") hasNonAriaReference = true;
|
|
57437
|
-
return;
|
|
57438
|
-
}
|
|
57439
|
-
if (isNodeOfType(cursor, "JSXAttribute")) {
|
|
57440
|
-
if (isEventHandlerAttribute(cursor)) return;
|
|
57441
|
-
if (!isInsideIdOrAriaAttribute(node)) hasNonAriaReference = true;
|
|
57442
|
-
return;
|
|
57443
|
-
}
|
|
57444
|
-
if (isNodeOfType(cursor, "ReturnStatement")) {
|
|
57445
|
-
hasNonAriaReference = true;
|
|
57446
|
-
return;
|
|
57447
|
-
}
|
|
57448
|
-
cursor = cursor.parent;
|
|
57449
|
-
}
|
|
57450
|
-
});
|
|
57451
|
-
return hasNonAriaReference ? stateIdentifier.name : null;
|
|
57452
|
-
};
|
|
57453
|
-
const isExactViewportSubscriptionEffect = (context, effectCall, callback) => {
|
|
57454
|
-
if (!isReactApiCall(effectCall, USE_EFFECT_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return false;
|
|
57455
|
-
if (!isFunctionLike$1(callback) || callback.async || !isNodeOfType(callback.body, "BlockStatement")) return false;
|
|
57456
|
-
const statements = getCallbackStatements(callback);
|
|
57457
|
-
if (statements.length !== 4) return false;
|
|
57458
|
-
const handlerDeclaration = statements[0];
|
|
57459
|
-
if (!isNodeOfType(handlerDeclaration, "VariableDeclaration") || handlerDeclaration.kind !== "const" || handlerDeclaration.declarations?.length !== 1) return false;
|
|
57460
|
-
const handlerDeclarator = handlerDeclaration.declarations[0];
|
|
57461
|
-
if (!isNodeOfType(handlerDeclarator.id, "Identifier") || !isFunctionLike$1(handlerDeclarator.init)) return false;
|
|
57462
|
-
const handlerStatements = getCallbackStatements(handlerDeclarator.init);
|
|
57463
|
-
if (handlerStatements.length !== 1) return false;
|
|
57464
|
-
const handlerSetter = getDirectWindowWidthSetter(context, unwrapReturnExpression(handlerStatements[0]));
|
|
57465
|
-
const subscribedHandler = getResizeListenerHandler(context, statements[1], "addEventListener");
|
|
57466
|
-
const immediateSetter = getDirectWindowWidthSetter(context, statements[2]);
|
|
57467
|
-
const cleanupHandler = getCleanupResizeHandler(context, statements[3]);
|
|
57468
|
-
if (!handlerSetter || !subscribedHandler || !immediateSetter || !cleanupHandler) return false;
|
|
57469
|
-
const handlerSymbol = context.scopes.symbolFor(handlerDeclarator.id);
|
|
57470
|
-
if (!handlerSymbol || context.scopes.symbolFor(subscribedHandler) !== handlerSymbol || context.scopes.symbolFor(cleanupHandler) !== handlerSymbol) return false;
|
|
57471
|
-
if (!isNodeOfType(handlerSetter.callee, "Identifier") || !isNodeOfType(immediateSetter.callee, "Identifier") || context.scopes.symbolFor(handlerSetter.callee) !== context.scopes.symbolFor(immediateSetter.callee)) return false;
|
|
57472
|
-
const componentFunction = findEnclosingFunction$1(effectCall);
|
|
57473
|
-
if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return false;
|
|
57474
|
-
return findExactViewportState(context, componentFunction, immediateSetter) !== null;
|
|
57475
|
-
};
|
|
57476
56784
|
const renderingHydrationNoFlicker = defineRule({
|
|
57477
56785
|
id: "rendering-hydration-no-flicker",
|
|
57478
56786
|
title: "useEffect setState flashes on mount",
|
|
@@ -57485,14 +56793,7 @@ const renderingHydrationNoFlicker = defineRule({
|
|
|
57485
56793
|
if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
|
|
57486
56794
|
const callback = getEffectCallback(node);
|
|
57487
56795
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
57488
|
-
|
|
57489
|
-
context.report({
|
|
57490
|
-
node,
|
|
57491
|
-
message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
|
|
57492
|
-
});
|
|
57493
|
-
return;
|
|
57494
|
-
}
|
|
57495
|
-
const bodyStatements = getCallbackStatements(callback);
|
|
56796
|
+
const bodyStatements = (isNodeOfType(callback.body, "BlockStatement") ? callback.body.body ?? [] : [callback.body]).filter((statement) => !isNoOpStatement(statement));
|
|
57496
56797
|
if (bodyStatements.length !== 1) return;
|
|
57497
56798
|
const soleStatement = bodyStatements[0];
|
|
57498
56799
|
if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
|
|
@@ -67324,6 +66625,14 @@ const isStateKey = (key) => {
|
|
|
67324
66625
|
if (isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value === "state";
|
|
67325
66626
|
return false;
|
|
67326
66627
|
};
|
|
66628
|
+
const findEnclosingClass = (node) => {
|
|
66629
|
+
let ancestor = node.parent;
|
|
66630
|
+
while (ancestor) {
|
|
66631
|
+
if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
|
|
66632
|
+
ancestor = ancestor.parent ?? null;
|
|
66633
|
+
}
|
|
66634
|
+
return null;
|
|
66635
|
+
};
|
|
67327
66636
|
const isInConstructor = (node) => {
|
|
67328
66637
|
let ancestor = node.parent;
|
|
67329
66638
|
while (ancestor) {
|