oxlint-plugin-react-doctor 0.7.9-dev.2979a9b → 0.7.9-dev.2ba83c3
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 +21 -307
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8959,7 +8959,7 @@ const isReactNamespaceImport = (identifier, scopes) => {
|
|
|
8959
8959
|
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
8960
8960
|
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
8961
8961
|
};
|
|
8962
|
-
const isReactNamespaceReceiver
|
|
8962
|
+
const isReactNamespaceReceiver = (receiver, scopes, options) => {
|
|
8963
8963
|
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
8964
8964
|
if (isReactNamespaceImport(receiver, scopes)) return true;
|
|
8965
8965
|
return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
|
|
@@ -8972,7 +8972,7 @@ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) =>
|
|
|
8972
8972
|
for (const property of pattern.properties) {
|
|
8973
8973
|
if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
|
|
8974
8974
|
const propertyName = getStaticPropertyKeyName(property);
|
|
8975
|
-
return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver
|
|
8975
|
+
return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver(stripParenExpression(symbol.initializer), scopes, options));
|
|
8976
8976
|
}
|
|
8977
8977
|
return false;
|
|
8978
8978
|
};
|
|
@@ -8996,7 +8996,7 @@ const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds
|
|
|
8996
8996
|
return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
|
|
8997
8997
|
}
|
|
8998
8998
|
if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
|
|
8999
|
-
return isReactNamespaceReceiver
|
|
8999
|
+
return isReactNamespaceReceiver(stripParenExpression(callee.object), scopes, options);
|
|
9000
9000
|
};
|
|
9001
9001
|
//#endregion
|
|
9002
9002
|
//#region src/plugin/utils/is-proven-browser-api-receiver.ts
|
|
@@ -30212,12 +30212,6 @@ const isReactNamespaceImportReference = (ref) => Boolean(ref?.resolved?.defs.som
|
|
|
30212
30212
|
const importDeclaration = declarationNode.parent;
|
|
30213
30213
|
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
|
|
30214
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
|
-
};
|
|
30221
30215
|
const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
|
|
30222
30216
|
if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) return false;
|
|
30223
30217
|
const callee = stripParenExpression(declarator.init.callee);
|
|
@@ -30226,20 +30220,24 @@ const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
|
|
|
30226
30220
|
if (!reference?.resolved) return callee.name === hookName;
|
|
30227
30221
|
return isReactNamedImportReference(reference, hookName);
|
|
30228
30222
|
}
|
|
30229
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
|
|
30230
|
-
|
|
30223
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
|
|
30224
|
+
const namespaceReference = getRef(analysis, callee.object);
|
|
30225
|
+
if (!namespaceReference?.resolved) return callee.object.name === "React";
|
|
30226
|
+
return isReactNamespaceImportReference(namespaceReference);
|
|
30231
30227
|
};
|
|
30232
30228
|
const isHookCallee$1 = (analysis, node, hookName) => {
|
|
30233
30229
|
if (!node) return false;
|
|
30234
30230
|
if (isNodeOfType(node, "Identifier")) {
|
|
30235
30231
|
if (node.name === hookName) return true;
|
|
30236
30232
|
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;
|
|
30233
|
+
const parent = node.parent;
|
|
30234
|
+
if (parent && isNodeOfType(parent, "MemberExpression") && isNodeOfType(parent.object, "Identifier") && parent.object.name === "React" && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
|
|
30240
30235
|
return false;
|
|
30241
30236
|
}
|
|
30242
|
-
if (isNodeOfType(node, "MemberExpression"))
|
|
30237
|
+
if (isNodeOfType(node, "MemberExpression")) {
|
|
30238
|
+
const receiver = stripParenExpression(node.object);
|
|
30239
|
+
return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
|
|
30240
|
+
}
|
|
30243
30241
|
return false;
|
|
30244
30242
|
};
|
|
30245
30243
|
const isUseEffect = (node) => {
|
|
@@ -30663,88 +30661,7 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
|
|
|
30663
30661
|
if (HANDLER_BINDING_NAME_PATTERN.test(bindingName)) return true;
|
|
30664
30662
|
return isSetterWiredToJsxHandler(componentFunction, bindingName);
|
|
30665
30663
|
};
|
|
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) => {
|
|
30664
|
+
const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
|
|
30748
30665
|
if (!setterRef.resolved) return false;
|
|
30749
30666
|
const componentFunction = findEnclosingFunction$1(effectNode);
|
|
30750
30667
|
if (!componentFunction) return false;
|
|
@@ -30753,11 +30670,6 @@ const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, incl
|
|
|
30753
30670
|
const identifier = reference.identifier;
|
|
30754
30671
|
if (isAstDescendant(identifier, effectNode)) continue;
|
|
30755
30672
|
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
30673
|
}
|
|
30762
30674
|
return false;
|
|
30763
30675
|
};
|
|
@@ -31647,7 +31559,7 @@ const areInMutuallyExclusiveBranches = (leftNode, rightNode) => {
|
|
|
31647
31559
|
}
|
|
31648
31560
|
return false;
|
|
31649
31561
|
};
|
|
31650
|
-
const collectEffectStateWriteFacts = (analysis,
|
|
31562
|
+
const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) => {
|
|
31651
31563
|
const frames = collectBoundedEffectExecutionFrames(analysis, effectNode, currentFilename);
|
|
31652
31564
|
if (frames.length === 0) return [];
|
|
31653
31565
|
const effectHasCleanup = hasCleanup(analysis, effectNode);
|
|
@@ -31677,7 +31589,7 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
|
|
|
31677
31589
|
for (const returnedExpression of returnedExpressions) mergeEvidence(valueEvidence, collectValueEvidence(analysis, returnedExpression, updaterFrame, remainingValueCallFrames));
|
|
31678
31590
|
} else valueEvidence = collectValueEvidence(analysis, writtenValue, frame, remainingValueCallFrames);
|
|
31679
31591
|
const sourceReferences = [...valueEvidence.sourceReferences].filter((sourceReference) => getUseStateDecl(analysis, sourceReference) !== stateDeclarator);
|
|
31680
|
-
const hasIndependentWriter = hasUserInputSetterWriter(
|
|
31592
|
+
const hasIndependentWriter = hasUserInputSetterWriter(setterReference, effectNode, true);
|
|
31681
31593
|
const doesMatchStateInitializer = matchesStateInitializer(analysis, callExpression, stateDeclarator);
|
|
31682
31594
|
if (effectHasCleanup && (frame.isDeferred || valueEvidence.hasUnknownSource || valueEvidence.hasDeferredIntroducedValue || valueEvidence.readsExternalValue)) cleanupManagedStateDeclarators.add(stateDeclarator);
|
|
31683
31595
|
const isRenderKnownCopy = sourceReferences.length > 0 && !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue && !hasIndependentWriter;
|
|
@@ -31718,7 +31630,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
31718
31630
|
const dependencyReferences = getEffectDepsRefs(analysis, node);
|
|
31719
31631
|
if (!dependencyReferences) return;
|
|
31720
31632
|
if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
|
|
31721
|
-
for (const fact of collectEffectStateWriteFacts(analysis,
|
|
31633
|
+
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
31722
31634
|
if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
|
|
31723
31635
|
context.report({
|
|
31724
31636
|
node: fact.callExpression,
|
|
@@ -36292,7 +36204,7 @@ const noDerivedState = defineRule({
|
|
|
36292
36204
|
if (!isUseEffect(node)) return;
|
|
36293
36205
|
const analysis = getProgramAnalysis(node);
|
|
36294
36206
|
if (!analysis) return;
|
|
36295
|
-
for (const fact of collectEffectStateWriteFacts(analysis,
|
|
36207
|
+
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
36296
36208
|
if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
|
|
36297
36209
|
reportStateWrite(fact.callExpression, fact.stateDeclarator);
|
|
36298
36210
|
}
|
|
@@ -36312,7 +36224,7 @@ const noDerivedStateEffect = defineRule({
|
|
|
36312
36224
|
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
36313
36225
|
const analysis = getProgramAnalysis(node);
|
|
36314
36226
|
if (!analysis) return;
|
|
36315
|
-
if (!collectEffectStateWriteFacts(analysis,
|
|
36227
|
+
if (!collectEffectStateWriteFacts(analysis, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
|
|
36316
36228
|
context.report({
|
|
36317
36229
|
node,
|
|
36318
36230
|
message: "You pay an extra render for state you can derive from other values."
|
|
@@ -41482,7 +41394,7 @@ const noInitializeState = defineRule({
|
|
|
41482
41394
|
if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
|
|
41483
41395
|
const analysis = getProgramAnalysis(node);
|
|
41484
41396
|
if (!analysis) return;
|
|
41485
|
-
for (const fact of collectEffectStateWriteFacts(analysis,
|
|
41397
|
+
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
41486
41398
|
if (!fact.isRenderKnownCopy || fact.matchesStateInitializer || fact.resetsSourceState) continue;
|
|
41487
41399
|
const stateName = getStateName(fact.stateDeclarator);
|
|
41488
41400
|
context.report({
|
|
@@ -57047,39 +56959,8 @@ const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node,
|
|
|
57047
56959
|
destructureIndex: 1
|
|
57048
56960
|
});
|
|
57049
56961
|
//#endregion
|
|
57050
|
-
//#region src/plugin/utils/unwrap-return-expression.ts
|
|
57051
|
-
const unwrapReturnExpression = (node) => isNodeOfType(node, "ReturnStatement") && node.argument ? node.argument : node;
|
|
57052
|
-
//#endregion
|
|
57053
56962
|
//#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
|
|
57054
56963
|
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
|
-
};
|
|
57083
56964
|
const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
|
|
57084
56965
|
let readsCurrent = false;
|
|
57085
56966
|
walkAst(argument, (child) => {
|
|
@@ -57131,166 +57012,6 @@ const isStateUsedOnlyInIdOrAriaAttributes = (setterCall, setterName) => {
|
|
|
57131
57012
|
});
|
|
57132
57013
|
return referenceCount > 0 && !nonAriaReferenceFound;
|
|
57133
57014
|
};
|
|
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
|
-
};
|
|
57294
57015
|
const renderingHydrationNoFlicker = defineRule({
|
|
57295
57016
|
id: "rendering-hydration-no-flicker",
|
|
57296
57017
|
title: "useEffect setState flashes on mount",
|
|
@@ -57303,14 +57024,7 @@ const renderingHydrationNoFlicker = defineRule({
|
|
|
57303
57024
|
if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
|
|
57304
57025
|
const callback = getEffectCallback(node);
|
|
57305
57026
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
57306
|
-
|
|
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);
|
|
57027
|
+
const bodyStatements = (isNodeOfType(callback.body, "BlockStatement") ? callback.body.body ?? [] : [callback.body]).filter((statement) => !isNoOpStatement(statement));
|
|
57314
57028
|
if (bodyStatements.length !== 1) return;
|
|
57315
57029
|
const soleStatement = bodyStatements[0];
|
|
57316
57030
|
if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
|