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.
Files changed (2) hide show
  1. package/dist/index.js +21 -307
  2. 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$1 = (receiver, scopes, options) => {
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$1(stripParenExpression(symbol.initializer), scopes, options));
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$1(stripParenExpression(callee.object), scopes, options);
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
- return isReactNamespaceReceiver(analysis, callee.object);
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 receiverRoot = findTransparentExpressionRoot(node);
30238
- const parent = receiverRoot.parent;
30239
- if (parent && isNodeOfType(parent, "MemberExpression") && parent.object === receiverRoot && isReactNamespaceReceiver(analysis, node) && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
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")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
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 isSynchronousFunction = (functionNode) => {
30667
- const functionMetadata = functionNode;
30668
- return functionMetadata.async !== true && functionMetadata.generator !== true;
30669
- };
30670
- const findBindingVariable = (analysis, bindingIdentifier) => {
30671
- for (const scope of analysis.scopeManager.scopes) for (const variable of scope.variables) if (variable.identifiers.includes(bindingIdentifier)) return variable;
30672
- return null;
30673
- };
30674
- const getImmutableFunctionVariable = (analysis, componentFunction, functionNode) => {
30675
- if (!isSynchronousFunction(functionNode) || !isAstDescendant(functionNode, componentFunction)) return null;
30676
- const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
30677
- if (!bindingIdentifier) return null;
30678
- const variable = findBindingVariable(analysis, bindingIdentifier);
30679
- if (!variable || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && !reference.init)) return null;
30680
- const definition = variable.defs[0];
30681
- if (definition.type === "FunctionName") return definition.node === functionNode ? variable : null;
30682
- if (definition.type !== "Variable") return null;
30683
- const declarator = definition.node;
30684
- if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return null;
30685
- if (declarator.init === functionNode) return variable;
30686
- if (isNodeOfType(declarator.init, "CallExpression") && declarator.init.arguments?.[0] === functionNode && isGenuineReactHookDeclarator(analysis, declarator, "useCallback")) return variable;
30687
- return null;
30688
- };
30689
- const getJsxEventValueAttribute = (identifier) => {
30690
- const expression = findTransparentExpressionRoot(identifier);
30691
- const expressionContainer = expression.parent;
30692
- if (!isNodeOfType(expressionContainer, "JSXExpressionContainer") || expressionContainer.expression !== expression) return null;
30693
- const attribute = expressionContainer.parent;
30694
- if (!isNodeOfType(attribute, "JSXAttribute")) return null;
30695
- const attributeName = getJsxAttributeName(attribute.name);
30696
- return attributeName && isEventHandlerName(attributeName) ? attribute : null;
30697
- };
30698
- const getInlineJsxEventCallbackAttribute = (callExpression) => {
30699
- const callbackFunction = findEnclosingFunction$1(callExpression);
30700
- if (!callbackFunction || !isSynchronousFunction(callbackFunction)) return null;
30701
- return getJsxEventValueAttribute(callbackFunction);
30702
- };
30703
- const isReactHookDependencyReference = (identifier) => {
30704
- const expression = findTransparentExpressionRoot(identifier);
30705
- const dependencyArray = expression.parent;
30706
- if (!isNodeOfType(dependencyArray, "ArrayExpression") || !(dependencyArray.elements ?? []).includes(expression)) return false;
30707
- const hookCall = dependencyArray.parent;
30708
- if (!isNodeOfType(hookCall, "CallExpression") || hookCall.arguments?.[1] !== dependencyArray) return false;
30709
- const callee = hookCall.callee;
30710
- if (isNodeOfType(callee, "Identifier")) return /^use[A-Z0-9]/.test(callee.name);
30711
- return Boolean(isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && /^use[A-Z0-9]/.test(callee.property.name));
30712
- };
30713
- const hasReachableJsxEventCallPath = (analysis, context, componentFunction, functionVariable, visitedVariables) => {
30714
- if (visitedVariables.has(functionVariable)) return false;
30715
- const nextVisitedVariables = new Set(visitedVariables).add(functionVariable);
30716
- const callExpressions = [];
30717
- let hasDirectJsxEventReference = false;
30718
- for (const reference of functionVariable.references) {
30719
- if (reference.init) continue;
30720
- const identifier = reference.identifier;
30721
- if (reference.isWrite()) return false;
30722
- const jsxEventValueAttribute = getJsxEventValueAttribute(identifier);
30723
- if (jsxEventValueAttribute) {
30724
- if (isNodeReachableWithinFunction(jsxEventValueAttribute, context)) hasDirectJsxEventReference = true;
30725
- continue;
30726
- }
30727
- if (isReactHookDependencyReference(identifier)) continue;
30728
- const callExpression = getCallExpr(reference);
30729
- if (!callExpression) return false;
30730
- const jsxEventCallbackAttribute = getInlineJsxEventCallbackAttribute(callExpression);
30731
- if (jsxEventCallbackAttribute) {
30732
- if (isNodeReachableWithinFunction(callExpression, context) && isNodeReachableWithinFunction(jsxEventCallbackAttribute, context)) hasDirectJsxEventReference = true;
30733
- continue;
30734
- }
30735
- callExpressions.push(callExpression);
30736
- }
30737
- if (hasDirectJsxEventReference) return true;
30738
- for (const callExpression of callExpressions) {
30739
- if (!isNodeReachableWithinFunction(callExpression, context)) continue;
30740
- const callerFunction = findEnclosingFunction$1(callExpression);
30741
- if (!callerFunction || callerFunction === componentFunction) continue;
30742
- const callerVariable = getImmutableFunctionVariable(analysis, componentFunction, callerFunction);
30743
- if (callerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, callerVariable, nextVisitedVariables)) return true;
30744
- }
30745
- return false;
30746
- };
30747
- const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, includeDeferredWriters = false) => {
30664
+ 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, context, effectNode, currentFilename) => {
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(analysis, context, setterReference, effectNode, true);
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, context, node, context.filename)) {
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, context, node, context.filename)) {
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, context, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
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, context, node, context.filename)) {
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
- if (isExactViewportSubscriptionEffect(context, node, callback)) {
57307
- context.report({
57308
- node,
57309
- message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
57310
- });
57311
- return;
57312
- }
57313
- const bodyStatements = getCallbackStatements(callback);
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.9-dev.2979a9b",
3
+ "version": "0.7.9-dev.2ba83c3",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",