oxlint-plugin-react-doctor 0.7.9-dev.c7f61cd → 0.7.9-dev.d7d38d7

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 +90 -566
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -579,22 +579,6 @@ const EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS = new Set([
579
579
  "ResizeObserver",
580
580
  "PerformanceObserver"
581
581
  ]);
582
- const EXTERNAL_SYNC_DOM_MEMBER_METHOD_NAMES = new Set([
583
- "blur",
584
- "focus",
585
- "getBoundingClientRect",
586
- "getClientRects",
587
- "measure",
588
- "measureInWindow",
589
- "measureLayout",
590
- "scroll",
591
- "scrollBy",
592
- "scrollIntoView",
593
- "scrollTo",
594
- "select",
595
- "setRangeText",
596
- "setSelectionRange"
597
- ]);
598
582
  const STORAGE_OBJECTS = new Set(["localStorage", "sessionStorage"]);
599
583
  //#endregion
600
584
  //#region src/plugin/constants/react.ts
@@ -20012,28 +19996,6 @@ const jsLengthCheckFirst = defineRule({
20012
19996
  } })
20013
19997
  });
20014
19998
  //#endregion
20015
- //#region src/plugin/utils/is-proven-global-namespace-reference.ts
20016
- const isProvenGlobalObjectReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
20017
- const strippedExpression = stripParenExpression(expression);
20018
- if (!isNodeOfType(strippedExpression, "Identifier")) return false;
20019
- if ((strippedExpression.name === "globalThis" || strippedExpression.name === "window" || strippedExpression.name === "self" || strippedExpression.name === "global") && scopes.isGlobalReference(strippedExpression)) return true;
20020
- const symbol = scopes.symbolFor(strippedExpression);
20021
- if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
20022
- visitedSymbolIds.add(symbol.id);
20023
- return isProvenGlobalObjectReference(symbol.initializer, scopes, visitedSymbolIds);
20024
- };
20025
- const isProvenGlobalNamespaceReference = (expression, namespaceName, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
20026
- const strippedExpression = stripParenExpression(expression);
20027
- if (isNodeOfType(strippedExpression, "Identifier")) {
20028
- if (strippedExpression.name === namespaceName && scopes.isGlobalReference(strippedExpression)) return true;
20029
- const symbol = scopes.symbolFor(strippedExpression);
20030
- if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
20031
- visitedSymbolIds.add(symbol.id);
20032
- return isProvenGlobalNamespaceReference(symbol.initializer, namespaceName, scopes, visitedSymbolIds);
20033
- }
20034
- return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isProvenGlobalObjectReference(strippedExpression.object, scopes);
20035
- };
20036
- //#endregion
20037
19999
  //#region src/plugin/rules/js-performance/js-min-max-loop.ts
20038
20000
  const builtinMutationByProgram = /* @__PURE__ */ new WeakMap();
20039
20001
  const RUNTIMELESS_SYMBOL_KINDS = new Set(["ts-interface", "ts-type-alias"]);
@@ -20090,6 +20052,26 @@ const isSafeFreshNumericArray = (arrayExpression) => {
20090
20052
  }
20091
20053
  return !(didFindPositiveZero && didFindNegativeZero);
20092
20054
  };
20055
+ const isGlobalObjectReference = (expression, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
20056
+ const strippedExpression = stripParenExpression(expression);
20057
+ if (!isNodeOfType(strippedExpression, "Identifier")) return false;
20058
+ if ((strippedExpression.name === "globalThis" || strippedExpression.name === "window" || strippedExpression.name === "self" || strippedExpression.name === "global") && scopes.isGlobalReference(strippedExpression)) return true;
20059
+ const symbol = scopes.symbolFor(strippedExpression);
20060
+ if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbols.has(symbol.id)) return false;
20061
+ visitedSymbols.add(symbol.id);
20062
+ return isGlobalObjectReference(symbol.initializer, scopes, visitedSymbols);
20063
+ };
20064
+ const resolvesToGlobalNamespace = (expression, namespaceName, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
20065
+ const strippedExpression = stripParenExpression(expression);
20066
+ if (isNodeOfType(strippedExpression, "Identifier")) {
20067
+ if (strippedExpression.name === namespaceName && scopes.isGlobalReference(strippedExpression)) return true;
20068
+ const symbol = scopes.symbolFor(strippedExpression);
20069
+ if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbols.has(symbol.id)) return false;
20070
+ visitedSymbols.add(symbol.id);
20071
+ return resolvesToGlobalNamespace(symbol.initializer, namespaceName, scopes, visitedSymbols);
20072
+ }
20073
+ return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isGlobalObjectReference(strippedExpression.object, scopes);
20074
+ };
20093
20075
  const resolvesToGlobalMethod = (expression, namespaceName, methodNames, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
20094
20076
  const strippedExpression = stripParenExpression(expression);
20095
20077
  if (isNodeOfType(strippedExpression, "Identifier")) {
@@ -20098,7 +20080,7 @@ const resolvesToGlobalMethod = (expression, namespaceName, methodNames, scopes,
20098
20080
  visitedSymbols.add(symbol.id);
20099
20081
  return resolvesToGlobalMethod(symbol.initializer, namespaceName, methodNames, scopes, visitedSymbols);
20100
20082
  }
20101
- return isNodeOfType(strippedExpression, "MemberExpression") && methodNames.has(getStaticPropertyName(strippedExpression) ?? "") && isProvenGlobalNamespaceReference(strippedExpression.object, namespaceName, scopes);
20083
+ return isNodeOfType(strippedExpression, "MemberExpression") && methodNames.has(getStaticPropertyName(strippedExpression) ?? "") && resolvesToGlobalNamespace(strippedExpression.object, namespaceName, scopes);
20102
20084
  };
20103
20085
  const resolvesToNativeArrayPrototype = (expression, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
20104
20086
  const strippedExpression = stripParenExpression(expression);
@@ -20110,7 +20092,7 @@ const resolvesToNativeArrayPrototype = (expression, scopes, visitedSymbols = /*
20110
20092
  }
20111
20093
  if (isNodeOfType(strippedExpression, "MemberExpression")) {
20112
20094
  const propertyName = getStaticPropertyName(strippedExpression);
20113
- if (propertyName === "prototype") return isProvenGlobalNamespaceReference(strippedExpression.object, "Array", scopes);
20095
+ if (propertyName === "prototype") return resolvesToGlobalNamespace(strippedExpression.object, "Array", scopes);
20114
20096
  return propertyName === "__proto__" && isNodeOfType(stripParenExpression(strippedExpression.object), "ArrayExpression");
20115
20097
  }
20116
20098
  if (!isNodeOfType(strippedExpression, "CallExpression")) return false;
@@ -20121,23 +20103,23 @@ const resolvesToNativeArrayPrototype = (expression, scopes, visitedSymbols = /*
20121
20103
  const isGlobalNamespaceReplacementTarget = (target, namespaceName, scopes) => {
20122
20104
  const strippedTarget = stripParenExpression(target);
20123
20105
  if (isNodeOfType(strippedTarget, "Identifier")) return strippedTarget.name === namespaceName && scopes.isGlobalReference(strippedTarget);
20124
- return isNodeOfType(strippedTarget, "MemberExpression") && getStaticPropertyName(strippedTarget) === namespaceName && isProvenGlobalObjectReference(strippedTarget.object, scopes);
20106
+ return isNodeOfType(strippedTarget, "MemberExpression") && getStaticPropertyName(strippedTarget) === namespaceName && isGlobalObjectReference(strippedTarget.object, scopes);
20125
20107
  };
20126
20108
  const isUnsafeBuiltinMemberTarget = (target, targetFunction, scopes) => {
20127
20109
  const strippedTarget = stripParenExpression(target);
20128
20110
  if (!isNodeOfType(strippedTarget, "MemberExpression")) return false;
20129
20111
  const propertyName = getStaticPropertyName(strippedTarget);
20130
20112
  if (resolvesToNativeArrayPrototype(strippedTarget.object, scopes)) return propertyName === null || propertyName === "sort";
20131
- if (isProvenGlobalNamespaceReference(strippedTarget.object, "Math", scopes)) return propertyName === null || propertyName === targetFunction;
20132
- return isProvenGlobalObjectReference(strippedTarget.object, scopes) && (propertyName === null || propertyName === "Math");
20113
+ if (resolvesToGlobalNamespace(strippedTarget.object, "Math", scopes)) return propertyName === null || propertyName === targetFunction;
20114
+ return isGlobalObjectReference(strippedTarget.object, scopes) && (propertyName === null || propertyName === "Math");
20133
20115
  };
20134
20116
  const isUnsafeBuiltinMutationApiCall = (callExpression, targetFunction, scopes) => {
20135
20117
  const target = callExpression.arguments[0];
20136
20118
  if (!target) return false;
20137
20119
  let propertyName = null;
20138
20120
  if (resolvesToNativeArrayPrototype(target, scopes)) propertyName = "sort";
20139
- else if (isProvenGlobalNamespaceReference(target, "Math", scopes)) propertyName = targetFunction;
20140
- else if (isProvenGlobalObjectReference(target, scopes)) propertyName = "Math";
20121
+ else if (resolvesToGlobalNamespace(target, "Math", scopes)) propertyName = targetFunction;
20122
+ else if (isGlobalObjectReference(target, scopes)) propertyName = "Math";
20141
20123
  if (!propertyName) return false;
20142
20124
  const canObjectExpressionSetProperty = (properties) => {
20143
20125
  if (!isNodeOfType(properties, "ObjectExpression")) return true;
@@ -20188,7 +20170,7 @@ const hasUnsafeMathBinding = (node, scopes) => {
20188
20170
  let scope = scopes.scopeFor(node);
20189
20171
  while (scope) {
20190
20172
  const symbol = scope.symbolsByName.get("Math");
20191
- if (symbol && !RUNTIMELESS_SYMBOL_KINDS.has(symbol.kind)) return !(symbol.kind === "const" && symbol.initializer && isProvenGlobalNamespaceReference(symbol.initializer, "Math", scopes));
20173
+ if (symbol && !RUNTIMELESS_SYMBOL_KINDS.has(symbol.kind)) return !(symbol.kind === "const" && symbol.initializer && resolvesToGlobalNamespace(symbol.initializer, "Math", scopes));
20192
20174
  scope = scope.parent;
20193
20175
  }
20194
20176
  return false;
@@ -28485,11 +28467,7 @@ const isProvenNativeReadMethod = (ref, methodName) => Boolean(ref.resolved?.defs
28485
28467
  }));
28486
28468
  //#endregion
28487
28469
  //#region src/plugin/rules/state-and-effects/utils/effect/react.ts
28488
- const KNOWN_COMPONENT_WRAPPER_NAMES = new Set([
28489
- "memo",
28490
- "forwardRef",
28491
- "observer"
28492
- ]);
28470
+ const KNOWN_PURE_HOC_NAMES = new Set(["memo", "forwardRef"]);
28493
28471
  const startsWithUppercase = (name) => Boolean(name && name.length > 0 && name[0] >= "A" && name[0] <= "Z");
28494
28472
  const isReactFunctionalComponent = (node) => {
28495
28473
  if (!node) return false;
@@ -28511,7 +28489,7 @@ const isReactFunctionalHOC = (analysis, node) => {
28511
28489
  const isWrappedInline = () => {
28512
28490
  if (!isNodeOfType(init, "CallExpression")) return false;
28513
28491
  if (!isNodeOfType(init.callee, "Identifier")) return false;
28514
- if (KNOWN_COMPONENT_WRAPPER_NAMES.has(init.callee.name)) return false;
28492
+ if (KNOWN_PURE_HOC_NAMES.has(init.callee.name)) return false;
28515
28493
  const firstArg = init.arguments?.[0];
28516
28494
  if (!firstArg) return false;
28517
28495
  return isNodeOfType(firstArg, "ArrowFunctionExpression") || isNodeOfType(firstArg, "FunctionExpression");
@@ -28531,7 +28509,7 @@ const isReactFunctionalHOC = (analysis, node) => {
28531
28509
  if (!args.includes(refId)) continue;
28532
28510
  const callee = parent.callee;
28533
28511
  const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "CallExpression") && isNodeOfType(callee.callee, "Identifier") ? callee.callee.name : null;
28534
- if (calleeName != null && !KNOWN_COMPONENT_WRAPPER_NAMES.has(calleeName)) return true;
28512
+ if (calleeName != null && !KNOWN_PURE_HOC_NAMES.has(calleeName)) return true;
28535
28513
  }
28536
28514
  return false;
28537
28515
  };
@@ -32959,18 +32937,6 @@ const isProvenIntrinsicJsxElement = (openingElement, scopes) => {
32959
32937
  return isIntrinsicValue(openingElement.name);
32960
32938
  };
32961
32939
  //#endregion
32962
- //#region src/plugin/utils/is-inline-intrinsic-ref-callback.ts
32963
- const isInlineIntrinsicRefCallback = (functionNode, scopes) => {
32964
- const functionExpression = findTransparentExpressionRoot(functionNode);
32965
- if (!isFunctionLike$1(functionExpression) || functionExpression.async || functionExpression.generator) return false;
32966
- const container = functionExpression.parent;
32967
- if (!container || !isNodeOfType(container, "JSXExpressionContainer")) return false;
32968
- const attribute = container.parent;
32969
- if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
32970
- const openingElement = attribute.parent;
32971
- return Boolean(openingElement && isNodeOfType(openingElement, "JSXOpeningElement") && isProvenIntrinsicJsxElement(openingElement, scopes));
32972
- };
32973
- //#endregion
32974
32940
  //#region src/plugin/rules/react-builtins/is-safe-create-ref-callback-current-write.ts
32975
32941
  const pathStartsWith$1 = (propertyPath, prefix) => prefix.every((propertyName, index) => propertyPath[index] === propertyName);
32976
32942
  const collectMemberExpression = (identifier) => {
@@ -32981,6 +32947,16 @@ const collectMemberExpression = (identifier) => {
32981
32947
  }
32982
32948
  return expression;
32983
32949
  };
32950
+ const isInlineIntrinsicRefCallback = (functionNode, scopes) => {
32951
+ const functionExpression = findTransparentExpressionRoot(functionNode);
32952
+ if (!isFunctionLike$1(functionExpression) || functionExpression.async || functionExpression.generator) return false;
32953
+ const container = functionExpression.parent;
32954
+ if (!container || !isNodeOfType(container, "JSXExpressionContainer")) return false;
32955
+ const attribute = container.parent;
32956
+ if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
32957
+ const openingElement = attribute.parent;
32958
+ return Boolean(openingElement && isNodeOfType(openingElement, "JSXOpeningElement") && isProvenIntrinsicJsxElement(openingElement, scopes));
32959
+ };
32984
32960
  const isSafeCreateRefCallbackCurrentWrite = (referenceNode, accessedPropertyPath, targetPropertyPath, scopes) => {
32985
32961
  if (accessedPropertyPath.length !== targetPropertyPath.length + 1 || !pathStartsWith$1(accessedPropertyPath, targetPropertyPath) || accessedPropertyPath[targetPropertyPath.length] !== "current") return false;
32986
32962
  const memberExpression = collectMemberExpression(referenceNode);
@@ -35924,178 +35900,17 @@ const visitSynchronousFunctionBodies = (analysisFunctions, visitor) => {
35924
35900
  walkInsideStatementBlocks(analysisFunction.body, visitor);
35925
35901
  }
35926
35902
  };
35927
- const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
35928
- const unwrappedExpression = stripParenExpression(expression);
35929
- if (isNodeOfType(unwrappedExpression, "Literal")) {
35930
- const literalValue = unwrappedExpression.value;
35931
- if (literalValue === null || typeof literalValue === "boolean" || typeof literalValue === "number" || typeof literalValue === "string") return { value: literalValue };
35932
- return null;
35933
- }
35934
- if (isNodeOfType(unwrappedExpression, "Identifier")) {
35935
- if (scopes.symbolFor(unwrappedExpression)?.id === stateSymbolId) return stateValue;
35936
- if (unwrappedExpression.name === "undefined" && scopes.isGlobalReference(unwrappedExpression)) return { value: void 0 };
35937
- const immutableSymbol = scopes.symbolFor(unwrappedExpression);
35938
- if (immutableSymbol?.kind !== "const" || !immutableSymbol.initializer || !isNodeOfType(immutableSymbol.declarationNode, "VariableDeclarator") || immutableSymbol.declarationNode.id !== immutableSymbol.bindingIdentifier || immutableSymbol.declarationNode.init !== immutableSymbol.initializer || immutableSymbol.references.some((reference) => reference.flag !== "read") || visitedSymbolIds.has(immutableSymbol.id)) return null;
35939
- return readStaticEffectValue(immutableSymbol.initializer, scopes, stateSymbolId, stateValue, new Set(visitedSymbolIds).add(immutableSymbol.id));
35940
- }
35941
- if (isNodeOfType(unwrappedExpression, "UnaryExpression")) {
35942
- if (unwrappedExpression.operator === "void") return { value: void 0 };
35943
- if (unwrappedExpression.operator !== "!") return null;
35944
- const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds);
35945
- return argumentValue ? { value: !argumentValue.value } : null;
35946
- }
35947
- if (isNodeOfType(unwrappedExpression, "CallExpression")) {
35948
- if (isNodeOfType(unwrappedExpression.callee, "Identifier") && unwrappedExpression.callee.name === "Boolean" && scopes.isGlobalReference(unwrappedExpression.callee) && unwrappedExpression.arguments.length === 1 && unwrappedExpression.arguments[0] && !isNodeOfType(unwrappedExpression.arguments[0], "SpreadElement")) {
35949
- const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds);
35950
- return argumentValue ? { value: Boolean(argumentValue.value) } : null;
35951
- }
35952
- return null;
35953
- }
35954
- if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
35955
- const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
35956
- if (!leftValue) return null;
35957
- if (unwrappedExpression.operator === "&&" && !leftValue.value) return leftValue;
35958
- if (unwrappedExpression.operator === "||" && leftValue.value) return leftValue;
35959
- if (unwrappedExpression.operator === "??" && leftValue.value !== null && leftValue.value !== void 0) return leftValue;
35960
- return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
35961
- }
35962
- if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
35963
- const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds);
35964
- if (!testValue) return null;
35965
- return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds);
35966
- }
35967
- if (isNodeOfType(unwrappedExpression, "MemberExpression") && unwrappedExpression.optional) {
35968
- const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds);
35969
- if (objectValue?.value === null || objectValue?.value === void 0) return { value: void 0 };
35970
- return null;
35971
- }
35972
- if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
35973
- const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
35974
- const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
35975
- if (!leftValue || !rightValue) return null;
35976
- if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==") {
35977
- const areEqual = leftValue.value === rightValue.value;
35978
- return { value: unwrappedExpression.operator === "===" ? areEqual : !areEqual };
35979
- }
35980
- if (unwrappedExpression.operator === "==" || unwrappedExpression.operator === "!=") {
35981
- const isLeftNullish = leftValue.value === null || leftValue.value === void 0;
35982
- const isRightNullish = rightValue.value === null || rightValue.value === void 0;
35983
- if (!isLeftNullish && !isRightNullish && typeof leftValue.value !== typeof rightValue.value) return null;
35984
- const areEqual = isLeftNullish || isRightNullish ? isLeftNullish && isRightNullish : leftValue.value === rightValue.value;
35985
- return { value: unwrappedExpression.operator === "==" ? areEqual : !areEqual };
35986
- }
35987
- }
35988
- return null;
35989
- };
35990
- const readStaticUpdaterReturnValue = (updater, scopes) => {
35991
- if (!isFunctionLike$1(updater) || updater.async || updater.generator) return null;
35992
- if (!isNodeOfType(updater.body, "BlockStatement")) return readStaticEffectValue(updater.body, scopes, null, null);
35993
- if (updater.body.body.length === 0) return { value: void 0 };
35994
- if (updater.body.body.length !== 1) return null;
35995
- const returnStatement = updater.body.body[0];
35996
- if (!isNodeOfType(returnStatement, "ReturnStatement")) return null;
35997
- if (!returnStatement.argument) return { value: void 0 };
35998
- return readStaticEffectValue(returnStatement.argument, scopes, null, null);
35999
- };
36000
- const readStaticSetterValue = (setterCall, scopes) => {
36001
- const argument = setterCall.arguments[0];
36002
- if (!argument) return { value: void 0 };
36003
- if (isNodeOfType(argument, "SpreadElement")) return null;
36004
- const updater = resolveExactLocalFunction(argument, scopes);
36005
- if (updater) return readStaticUpdaterReturnValue(updater, scopes);
36006
- return readStaticEffectValue(argument, scopes, null, null);
36007
- };
36008
- const collectStateWritesInEffect = (analysisFunctions, setterToStateName, scopes) => {
36009
- const stateWrites = /* @__PURE__ */ new Map();
35903
+ const collectWrittenStateNamesInEffect = (analysisFunctions, setterToStateName) => {
35904
+ const writtenStateNames = /* @__PURE__ */ new Set();
36010
35905
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
36011
35906
  if (!isNodeOfType(child, "CallExpression")) return;
36012
35907
  if (!isNodeOfType(child.callee, "Identifier")) return;
36013
35908
  const stateName = setterToStateName.get(child.callee.name);
36014
- if (!stateName) return;
36015
- const writeInfo = stateWrites.get(stateName) ?? {
36016
- values: /* @__PURE__ */ new Set(),
36017
- hasUnknownValue: false
36018
- };
36019
- const staticValue = readStaticSetterValue(child, scopes);
36020
- if (staticValue) writeInfo.values.add(staticValue.value);
36021
- else writeInfo.hasUnknownValue = true;
36022
- stateWrites.set(stateName, writeInfo);
35909
+ if (stateName) writtenStateNames.add(stateName);
36023
35910
  });
36024
- return stateWrites;
36025
- };
36026
- const isGlobalBooleanCall = (node, scopes) => {
36027
- return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Boolean" && scopes.isGlobalReference(node.callee);
36028
- };
36029
- const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes) => {
36030
- let currentNode = workNode;
36031
- while (currentNode.parent) {
36032
- const parentNode = currentNode.parent;
36033
- if (isFunctionLike$1(parentNode)) break;
36034
- if (isNodeOfType(parentNode, "IfStatement")) {
36035
- const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
36036
- if (testValue) {
36037
- if (currentNode === parentNode.consequent && !testValue.value) return false;
36038
- if (currentNode === parentNode.alternate && testValue.value) return false;
36039
- }
36040
- }
36041
- if (isNodeOfType(parentNode, "ConditionalExpression")) {
36042
- const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
36043
- if (testValue) {
36044
- if (currentNode === parentNode.consequent && !testValue.value) return false;
36045
- if (currentNode === parentNode.alternate && testValue.value) return false;
36046
- }
36047
- }
36048
- if (isNodeOfType(parentNode, "LogicalExpression") && currentNode === parentNode.right) {
36049
- const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue);
36050
- if (leftValue) {
36051
- if (parentNode.operator === "&&" && !leftValue.value) return false;
36052
- if (parentNode.operator === "||" && leftValue.value) return false;
36053
- if (parentNode.operator === "??" && leftValue.value !== null && leftValue.value !== void 0) return false;
36054
- }
36055
- }
36056
- if (isNodeOfType(parentNode, "BlockStatement")) {
36057
- const statementIndex = parentNode.body.findIndex((statement) => statement === currentNode);
36058
- if (statementIndex >= 0) for (let index = 0; index < statementIndex; index += 1) {
36059
- const earlierStatement = parentNode.body[index];
36060
- if (!isNodeOfType(earlierStatement, "IfStatement") || earlierStatement.alternate || !statementAlwaysExits(earlierStatement.consequent)) continue;
36061
- if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue)?.value) return false;
36062
- }
36063
- }
36064
- currentNode = parentNode;
36065
- }
36066
- return true;
36067
- };
36068
- const isReaderWorkNode = (node, analysisFunctions, scopes) => {
36069
- if (isNodeOfType(node, "CallExpression")) {
36070
- if (isGlobalBooleanCall(node, scopes)) return false;
36071
- const invokedFunction = resolveExactLocalFunction(node.callee, scopes);
36072
- return !invokedFunction || !analysisFunctions.has(invokedFunction);
36073
- }
36074
- return isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "ThrowStatement") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete";
36075
- };
36076
- const canStateWriteReachReaderWork = (writeInfo, readerEffect, stateSymbolId, scopes) => {
36077
- if (writeInfo.hasUnknownValue || stateSymbolId === null) return true;
36078
- for (const writtenValue of writeInfo.values) {
36079
- const stateValue = { value: writtenValue };
36080
- let didFindReachableWork = false;
36081
- visitSynchronousFunctionBodies(readerEffect.analysisFunctions, (child) => {
36082
- if (didFindReachableWork || !isReaderWorkNode(child, readerEffect.analysisFunctions, scopes)) return;
36083
- if (isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes)) didFindReachableWork = true;
36084
- });
36085
- if (didFindReachableWork) return true;
36086
- }
36087
- return false;
35911
+ return writtenStateNames;
36088
35912
  };
36089
35913
  const EMPTY_CLEANUP_NAME_SET = /* @__PURE__ */ new Set();
36090
- const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
36091
- "clear",
36092
- "delete",
36093
- "entries",
36094
- "get",
36095
- "has",
36096
- "keys",
36097
- "values"
36098
- ]);
36099
35914
  const isFunctionShapedReturn = (returnedValue, setterToStateName, isExplicitReturnStatement) => {
36100
35915
  if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
36101
35916
  if (isNodeOfType(returnedValue, "CallExpression")) {
@@ -36146,135 +35961,27 @@ const callsOpaqueExternalSetter = (analysisFunctions, setterToStateName) => {
36146
35961
  });
36147
35962
  return didFindOpaqueSetterCall;
36148
35963
  };
36149
- const isReactRefCall = (expression, scopes) => isNodeOfType(expression, "CallExpression") && (isReactApiCall(expression, "useRef", scopes, {
36150
- allowGlobalReactNamespace: true,
36151
- allowUnboundBareCalls: true,
36152
- resolveNamedAliases: true
36153
- }) || isReactApiCall(expression, "createRef", scopes, {
36154
- allowGlobalReactNamespace: true,
36155
- allowUnboundBareCalls: true,
36156
- resolveNamedAliases: true
36157
- }));
36158
- const getDirectReactRefSymbol = (rawExpression, scopes) => {
36159
- const expression = stripParenExpression(rawExpression);
36160
- if (!isNodeOfType(expression, "Identifier")) return null;
36161
- const symbol = scopes.symbolFor(expression);
36162
- if (!symbol) return null;
36163
- const initializer = getDirectUnreassignedInitializer(symbol);
36164
- return initializer && isReactRefCall(stripParenExpression(initializer), scopes) ? symbol : null;
36165
- };
36166
- const isReactNativeJsxElement = (openingElement, scopes) => {
36167
- if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
36168
- const symbol = scopes.symbolFor(openingElement.name);
36169
- const importDeclaration = symbol?.declarationNode.parent;
36170
- return Boolean(symbol?.kind === "import" && importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react-native");
36171
- };
36172
- const isDirectHostJsxRef = (symbol, scopes) => {
36173
- let hostRefCount = 0;
36174
- for (const reference of symbol.references) {
36175
- const expression = findTransparentExpressionRoot(reference.identifier);
36176
- const container = expression.parent;
36177
- if (isNodeOfType(container, "MemberExpression") && container.object === expression && getStaticPropertyName(container) === "current") continue;
36178
- if (!container || !isNodeOfType(container, "JSXExpressionContainer") || container.expression !== expression) return false;
36179
- const attribute = container.parent;
36180
- if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
36181
- const openingElement = attribute.parent;
36182
- if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement") || !isProvenIntrinsicJsxElement(openingElement, scopes) && !isReactNativeJsxElement(openingElement, scopes)) return false;
36183
- hostRefCount += 1;
36184
- }
36185
- return hostRefCount > 0;
36186
- };
36187
- const isIntrinsicRefCallbackParameter = (expression, scopes) => {
36188
- const identifier = stripParenExpression(expression);
36189
- if (!isNodeOfType(identifier, "Identifier")) return false;
36190
- const callback = findEnclosingFunction$1(identifier);
36191
- if (!callback || !isFunctionLike$1(callback) || !isInlineIntrinsicRefCallback(callback, scopes)) return false;
36192
- const rawFirstParameter = callback.params?.[0];
36193
- const firstParameter = isNodeOfType(rawFirstParameter, "AssignmentPattern") ? rawFirstParameter.left : rawFirstParameter;
36194
- const symbol = scopes.symbolFor(identifier);
36195
- return Boolean(firstParameter && symbol?.bindingIdentifier === firstParameter);
36196
- };
36197
- const getDirectReactRefCall = (symbol, scopes) => {
36198
- const initializer = getDirectUnreassignedInitializer(symbol);
36199
- if (!initializer) return null;
36200
- const expression = stripParenExpression(initializer);
36201
- return isNodeOfType(expression, "CallExpression") && isReactRefCall(expression, scopes) ? expression : null;
36202
- };
36203
- const storesOnlyIntrinsicRefCallbackValues = (symbol, scopes) => {
36204
- const initialValue = getDirectReactRefCall(symbol, scopes)?.arguments?.[0];
36205
- if (!initialValue || !isNodeOfType(initialValue, "NewExpression") || !isNodeOfType(initialValue.callee, "Identifier") || initialValue.callee.name !== "Map" || !scopes.isGlobalReference(initialValue.callee) || initialValue.arguments.length !== 0) return false;
36206
- let intrinsicValueWriteCount = 0;
36207
- for (const reference of symbol.references) {
36208
- const identifier = findTransparentExpressionRoot(reference.identifier);
36209
- const currentMember = identifier.parent;
36210
- if (!isNodeOfType(currentMember, "MemberExpression") || currentMember.object !== identifier || getStaticPropertyName(currentMember) !== "current") return false;
36211
- const currentExpression = findTransparentExpressionRoot(currentMember);
36212
- const methodMember = currentExpression.parent;
36213
- if (!isNodeOfType(methodMember, "MemberExpression") || methodMember.object !== currentExpression) return false;
36214
- const methodName = getStaticPropertyName(methodMember);
36215
- if (methodName === "size") continue;
36216
- const call = methodMember.parent;
36217
- if (!isNodeOfType(call, "CallExpression") || call.callee !== methodMember) return false;
36218
- if (methodName && NON_CONTAMINATING_MAP_METHOD_NAMES.has(methodName)) continue;
36219
- if (methodName !== "set") return false;
36220
- const storedValue = call.arguments[1];
36221
- if (!storedValue || isNodeOfType(storedValue, "SpreadElement") || !isIntrinsicRefCallbackParameter(storedValue, scopes)) return false;
36222
- intrinsicValueWriteCount += 1;
36223
- }
36224
- return intrinsicValueWriteCount > 0;
36225
- };
36226
- const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollectionValue = false, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
36227
- const expression = stripParenExpression(rawExpression);
36228
- if (isNodeOfType(expression, "Identifier")) {
36229
- const symbol = scopes.symbolFor(expression);
36230
- if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
36231
- const initializer = getDirectUnreassignedInitializer(symbol);
36232
- if (!initializer) return false;
36233
- visitedSymbolIds.add(symbol.id);
36234
- return isDerivedFromProvenDomRefCurrent(initializer, scopes, didReadCollectionValue, visitedSymbolIds);
36235
- }
36236
- if (isNodeOfType(expression, "MemberExpression")) {
36237
- if (getStaticPropertyName(expression) === "current") {
36238
- const symbol = getDirectReactRefSymbol(expression.object, scopes);
36239
- return Boolean(symbol && (isDirectHostJsxRef(symbol, scopes) || didReadCollectionValue && storesOnlyIntrinsicRefCallbackValues(symbol, scopes)));
36240
- }
36241
- return isDerivedFromProvenDomRefCurrent(expression.object, scopes, didReadCollectionValue, visitedSymbolIds);
36242
- }
36243
- if (!isNodeOfType(expression, "CallExpression")) return false;
36244
- const callee = stripParenExpression(expression.callee);
36245
- if (!isNodeOfType(callee, "MemberExpression")) return false;
36246
- return isDerivedFromProvenDomRefCurrent(callee.object, scopes, didReadCollectionValue || getStaticPropertyName(callee) === "get", visitedSymbolIds);
36247
- };
36248
- const isCommittedDomSyncNode = (node, scopes) => {
36249
- if (!isNodeOfType(node, "CallExpression")) return false;
36250
- const callee = stripParenExpression(node.callee);
36251
- if (!isNodeOfType(callee, "MemberExpression")) return false;
36252
- const propertyName = getStaticPropertyName(callee);
36253
- if (propertyName === null || !EXTERNAL_SYNC_DOM_MEMBER_METHOD_NAMES.has(propertyName)) return false;
36254
- return isDerivedFromProvenDomRefCurrent(callee.object, scopes) || isProvenBrowserApiReceiver(callee.object, "dom-event-target", scopes);
36255
- };
36256
35964
  const isExternalSyncNode = (node) => {
36257
35965
  if (isNodeOfType(node, "NewExpression")) return isNodeOfType(node.callee, "Identifier") && EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS.has(node.callee.name);
36258
35966
  if (isNodeOfType(node, "AssignmentExpression")) return isNodeOfType(node.left, "MemberExpression") && isNodeOfType(node.left.property, "Identifier") && node.left.property.name === "current";
36259
35967
  if (!isNodeOfType(node, "CallExpression")) return false;
36260
35968
  if (isNodeOfType(node.callee, "Identifier")) return EXTERNAL_SYNC_DIRECT_CALLEE_NAMES.has(node.callee.name);
36261
- if (!isNodeOfType(node.callee, "MemberExpression")) return false;
36262
- const propertyName = getStaticPropertyName(node.callee);
36263
- if (propertyName === null) return false;
35969
+ if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier")) return false;
35970
+ const propertyName = node.callee.property.name;
36264
35971
  if (EXTERNAL_SYNC_MEMBER_METHOD_NAMES.has(propertyName)) return true;
36265
35972
  if (isBrowserStorageReceiver(node.callee.object)) return true;
36266
35973
  if (!EXTERNAL_SYNC_AMBIGUOUS_HTTP_METHOD_NAMES.has(propertyName)) return false;
36267
35974
  const receiverRootName = getRootIdentifierName(node.callee.object);
36268
35975
  return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
36269
35976
  };
36270
- const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, scopes, allowCommittedDomSync) => {
35977
+ const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName) => {
36271
35978
  if (!isFunctionLike$1(effectCallback)) return false;
36272
35979
  if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
36273
35980
  if (isFunctionShapedReturn(effectCallback.body, setterToStateName, false)) return true;
36274
35981
  } else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, true)) return true;
36275
35982
  let didFindExternalCall = false;
36276
35983
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
36277
- if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
35984
+ if (isExternalSyncNode(child)) didFindExternalCall = true;
36278
35985
  });
36279
35986
  return didFindExternalCall;
36280
35987
  };
@@ -36290,45 +35997,32 @@ const noEffectChain = defineRule({
36290
35997
  const useStateBindings = collectUseStateBindings(componentBody);
36291
35998
  if (useStateBindings.length === 0) return;
36292
35999
  const setterToStateName = /* @__PURE__ */ new Map();
36293
- const stateSymbolIds = /* @__PURE__ */ new Map();
36294
- for (const binding of useStateBindings) {
36295
- setterToStateName.set(binding.setterName, binding.valueName);
36296
- if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
36297
- const stateIdentifier = binding.declarator.id.elements[0];
36298
- if (isNodeOfType(stateIdentifier, "Identifier")) {
36299
- const stateSymbol = context.scopes.symbolFor(stateIdentifier);
36300
- if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
36301
- }
36302
- }
36000
+ for (const binding of useStateBindings) setterToStateName.set(binding.setterName, binding.valueName);
36303
36001
  const storageSetterNames = collectStorageHookSetterNames(componentBody);
36304
36002
  const effectInfos = [];
36305
36003
  for (const effectCall of findTopLevelEffectCalls(componentBody)) {
36306
36004
  const callback = getEffectCallback(effectCall, context.scopes);
36307
36005
  if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
36308
36006
  const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
36309
- const stateWrites = collectStateWritesInEffect(analysisFunctions, setterToStateName, context.scopes);
36310
- const writtenStateNames = new Set(stateWrites.keys());
36007
+ const writtenStateNames = collectWrittenStateNamesInEffect(analysisFunctions, setterToStateName);
36311
36008
  effectInfos.push({
36312
36009
  node: effectCall,
36313
36010
  depNames: collectDepIdentifierNames(effectCall),
36314
- stateWrites,
36315
- analysisFunctions,
36316
- isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName, context.scopes, writtenStateNames.size === 0) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
36011
+ writtenStateNames,
36012
+ isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
36317
36013
  });
36318
36014
  }
36319
36015
  if (effectInfos.length < 2) return;
36320
36016
  const reportedNodes = /* @__PURE__ */ new Set();
36321
36017
  for (const writerEffect of effectInfos) {
36322
36018
  if (writerEffect.isExternalSync) continue;
36323
- if (writerEffect.stateWrites.size === 0) continue;
36019
+ if (writerEffect.writtenStateNames.size === 0) continue;
36324
36020
  for (const readerEffect of effectInfos) {
36325
36021
  if (readerEffect === writerEffect) continue;
36326
36022
  if (readerEffect.isExternalSync) continue;
36327
36023
  if (readerEffect.depNames.size === 0) continue;
36328
36024
  let chainedStateName = null;
36329
- for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
36330
- if (!readerEffect.depNames.has(writtenName)) continue;
36331
- if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
36025
+ for (const writtenName of writerEffect.writtenStateNames) if (readerEffect.depNames.has(writtenName)) {
36332
36026
  chainedStateName = writtenName;
36333
36027
  break;
36334
36028
  }
@@ -39995,18 +39689,6 @@ const functionContainsProvenReactHookCall = (functionNode, scopes) => {
39995
39689
  return containsReactHookCall;
39996
39690
  };
39997
39691
  //#endregion
39998
- //#region src/plugin/utils/function-returns-only-null.ts
39999
- const isNullExpression = (expression) => {
40000
- const candidate = stripParenExpression(expression);
40001
- return isNodeOfType(candidate, "Literal") && candidate.value === null;
40002
- };
40003
- const functionReturnsOnlyNull = (functionNode) => {
40004
- if (!isFunctionLike$1(functionNode)) return false;
40005
- if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
40006
- const returnStatements = collectFunctionReturnStatements(functionNode);
40007
- return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
40008
- };
40009
- //#endregion
40010
39692
  //#region src/plugin/utils/function-returns-props-children.ts
40011
39693
  const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
40012
39694
  if (!isFunctionLike$1(functionNode) || functionNode.params.length === 0) return false;
@@ -40039,8 +39721,17 @@ const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
40039
39721
  }, controlFlow);
40040
39722
  };
40041
39723
  //#endregion
40042
- //#region src/plugin/utils/function-has-react-component-evidence.ts
40043
- const functionHasReactComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
39724
+ //#region src/plugin/utils/function-returns-only-null.ts
39725
+ const isNullExpression = (expression) => {
39726
+ const candidate = stripParenExpression(expression);
39727
+ return isNodeOfType(candidate, "Literal") && candidate.value === null;
39728
+ };
39729
+ const functionReturnsOnlyNull = (functionNode) => {
39730
+ if (!isFunctionLike$1(functionNode)) return false;
39731
+ if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
39732
+ const returnStatements = collectFunctionReturnStatements(functionNode);
39733
+ return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
39734
+ };
40044
39735
  //#endregion
40045
39736
  //#region src/plugin/utils/is-proven-styled-component-expression.ts
40046
39737
  const findFactoryRoot = (node) => {
@@ -40073,16 +39764,17 @@ const isProvenStyledComponentExpression = (expression, scopes) => {
40073
39764
  //#region src/plugin/utils/is-proven-react-component-symbol.ts
40074
39765
  const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
40075
39766
  const LEGACY_REACT_COMPONENT_FACTORY_NAMES = new Set(["createClass", "createReactClass"]);
39767
+ const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
40076
39768
  const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
40077
39769
  const candidate = stripParenExpression(expression);
40078
- if (isInlineFunctionExpression(candidate)) return functionHasReactComponentEvidence(candidate, scopes, controlFlow);
39770
+ if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
40079
39771
  if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
40080
39772
  if (isProvenStyledComponentExpression(candidate, scopes)) return true;
40081
39773
  if (isNodeOfType(candidate, "Identifier")) {
40082
39774
  const symbol = scopes.symbolFor(candidate);
40083
39775
  if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
40084
39776
  visitedSymbolIds.add(symbol.id);
40085
- if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasReactComponentEvidence(symbol.declarationNode, scopes, controlFlow);
39777
+ if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
40086
39778
  if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
40087
39779
  return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
40088
39780
  }
@@ -40109,7 +39801,7 @@ const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentRefe
40109
39801
  for (const candidateSymbol of candidateSymbols) {
40110
39802
  if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
40111
39803
  if (isComponentDeclaration(candidateSymbol.declarationNode)) {
40112
- if (functionHasReactComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
39804
+ if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
40113
39805
  continue;
40114
39806
  }
40115
39807
  const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
@@ -43895,53 +43587,6 @@ const noPropCallbackInEffect = defineRule({
43895
43587
  });
43896
43588
  //#endregion
43897
43589
  //#region src/plugin/rules/state-and-effects/no-prop-callback-in-render.ts
43898
- const functionBindingSymbols = (functionNode, scopes) => {
43899
- let bindingIdentifier = null;
43900
- if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.id) bindingIdentifier = functionNode.id;
43901
- else {
43902
- let bindingExpression = findTransparentExpressionRoot(functionNode);
43903
- let parent = bindingExpression.parent;
43904
- while (isNodeOfType(parent, "CallExpression") && parent.arguments[0] === bindingExpression) {
43905
- const callee = parent.callee;
43906
- const wrapperName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") ? callee.property.name : null;
43907
- if (!isReactApiCall(parent, REACT_HOC_NAMES, scopes, {
43908
- allowGlobalReactNamespace: true,
43909
- resolveNamedAliases: true
43910
- }) && (!wrapperName || REACT_HOC_NAMES.has(wrapperName) || !COMPONENT_HOC_WRAPPER_NAMES.has(wrapperName))) break;
43911
- bindingExpression = findTransparentExpressionRoot(parent);
43912
- parent = bindingExpression.parent;
43913
- }
43914
- if (isNodeOfType(parent, "VariableDeclarator") && parent.init === bindingExpression && isNodeOfType(parent.id, "Identifier")) bindingIdentifier = parent.id;
43915
- }
43916
- if (!bindingIdentifier) return [];
43917
- let scope = scopes.scopeFor(functionNode);
43918
- while (scope) {
43919
- const symbols = scope.symbols.filter((symbol) => symbol.bindingIdentifier === bindingIdentifier);
43920
- if (symbols.length > 0) return symbols;
43921
- scope = scope.parent;
43922
- }
43923
- return [];
43924
- };
43925
- const symbolHasReactComponentUse = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
43926
- if (visitedSymbolIds.has(symbol.id)) return false;
43927
- visitedSymbolIds.add(symbol.id);
43928
- for (const reference of symbol.references) {
43929
- const identifier = reference.identifier;
43930
- if (hasSymbolWriteBefore(symbol, identifier, scopes)) continue;
43931
- const parent = identifier.parent;
43932
- if (isNodeOfType(parent, "JSXOpeningElement") && isNodeOfType(parent.name, "JSXIdentifier") && parent.name === identifier) return true;
43933
- const expression = findTransparentExpressionRoot(identifier);
43934
- const expressionParent = expression.parent;
43935
- if (isNodeOfType(expressionParent, "CallExpression") && expressionParent.arguments[0] === expression && isReactApiCall(expressionParent, "createElement", scopes, { resolveNamedAliases: true })) return true;
43936
- if (!isNodeOfType(expressionParent, "VariableDeclarator") || expressionParent.init !== expression || !isNodeOfType(expressionParent.id, "Identifier") || !isNodeOfType(expressionParent.parent, "VariableDeclaration") || expressionParent.parent.kind !== "const") continue;
43937
- const aliasSymbol = scopes.symbolFor(expressionParent.id);
43938
- if (aliasSymbol && symbolHasReactComponentUse(aliasSymbol, scopes, visitedSymbolIds)) return true;
43939
- }
43940
- return false;
43941
- };
43942
- const functionHasReactComponentUse = (functionNode, scopes) => {
43943
- return functionBindingSymbols(functionNode, scopes).some((symbol) => symbolHasReactComponentUse(symbol, scopes));
43944
- };
43945
43590
  const isPreservedThroughConciseArrow = (callExpression, scopes) => {
43946
43591
  let node = callExpression;
43947
43592
  let parent = node.parent;
@@ -43988,10 +43633,7 @@ const noPropCallbackInRender = defineRule({
43988
43633
  create: (context) => ({ CallExpression(node) {
43989
43634
  if (!isResultDiscardedCall(node)) return;
43990
43635
  if (isPreservedThroughConciseArrow(node, context.scopes)) return;
43991
- const renderPhaseOwner = findRenderPhaseComponentOrHook(node, context.scopes);
43992
- if (!renderPhaseOwner) return;
43993
- const renderPhaseOwnerName = componentOrHookDisplayNameForFunction(renderPhaseOwner);
43994
- if (!renderPhaseOwnerName || !isReactHookName(renderPhaseOwnerName) && !functionHasReactComponentEvidence(renderPhaseOwner, context.scopes, context.cfg) && !functionHasReactComponentUse(renderPhaseOwner, context.scopes)) return;
43636
+ if (!findRenderPhaseComponentOrHook(node, context.scopes)) return;
43995
43637
  const analysis = getProgramAnalysis(node);
43996
43638
  if (!analysis) return;
43997
43639
  const callee = stripParenExpression(node.callee);
@@ -44727,110 +44369,11 @@ const isSameRefCurrentMember = (node, refSymbol, scopes) => {
44727
44369
  return isNodeOfType(receiver, "Identifier") && resolveConstIdentifierAlias(receiver, scopes)?.id === refSymbol.id;
44728
44370
  };
44729
44371
  const isSameRefCurrentAlias = (node, refSymbol, scopes) => {
44730
- const expression = stripParenExpression(node);
44731
- if (isSameRefCurrentMember(expression, refSymbol, scopes)) return true;
44732
- if (!isNodeOfType(expression, "Identifier")) return false;
44733
- const aliasSymbol = scopes.symbolFor(expression);
44372
+ if (isSameRefCurrentMember(node, refSymbol, scopes)) return true;
44373
+ if (!isNodeOfType(node, "Identifier")) return false;
44374
+ const aliasSymbol = scopes.symbolFor(node);
44734
44375
  return aliasSymbol?.kind === "const" && aliasSymbol.initializer !== null && isSameRefCurrentMember(stripParenExpression(aliasSymbol.initializer), refSymbol, scopes);
44735
44376
  };
44736
- const resolveImmutableInitializationValue = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
44737
- const expression = stripParenExpression(node);
44738
- if (!isNodeOfType(expression, "Identifier")) return expression;
44739
- const symbol = scopes.symbolFor(expression);
44740
- if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || visitedSymbolIds.has(symbol.id)) return null;
44741
- visitedSymbolIds.add(symbol.id);
44742
- return resolveImmutableInitializationValue(symbol.initializer, scopes, visitedSymbolIds);
44743
- };
44744
- const isProvablyTruthyInitializationValue = (node, scopes) => {
44745
- const expression = resolveImmutableInitializationValue(node, scopes);
44746
- return Boolean(expression && (isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "ArrayExpression") || isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "ClassExpression")));
44747
- };
44748
- const getInitializationConstructorName = (node, scopes) => {
44749
- const expression = resolveImmutableInitializationValue(node, scopes);
44750
- if (!expression) return null;
44751
- if (isNodeOfType(expression, "NewExpression")) {
44752
- const callee = stripParenExpression(expression.callee);
44753
- return isNodeOfType(callee, "Identifier") ? callee.name : null;
44754
- }
44755
- return null;
44756
- };
44757
- const isClosedTruthyTypeDomain = (typeNode, initializationValue, scopes) => {
44758
- const initializationExpression = stripParenExpression(initializationValue);
44759
- if (isNodeOfType(typeNode, "TSTypeLiteral")) return isNodeOfType(initializationExpression, "ObjectExpression");
44760
- if (isNodeOfType(typeNode, "TSArrayType") || isNodeOfType(typeNode, "TSTupleType")) return isNodeOfType(initializationExpression, "ArrayExpression");
44761
- if (isNodeOfType(typeNode, "TSFunctionType") || isNodeOfType(typeNode, "TSConstructorType")) return isNodeOfType(initializationExpression, "ArrowFunctionExpression") || isNodeOfType(initializationExpression, "FunctionExpression") || isNodeOfType(initializationExpression, "ClassExpression");
44762
- if (isNodeOfType(typeNode, "TSObjectKeyword")) return true;
44763
- if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
44764
- const typeName = typeNode.typeName;
44765
- return isNodeOfType(typeName, "Identifier") && typeName.name === getInitializationConstructorName(initializationExpression, scopes);
44766
- };
44767
- const refHasClosedFalsySentinelDomain = (refSymbol, initializationValue, scopes) => {
44768
- const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
44769
- if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
44770
- const [initialValue] = initializer.arguments ?? [];
44771
- if (!initialValue || isNodeOfType(initialValue, "SpreadElement") || !isEmptySentinel(initialValue, scopes)) return false;
44772
- const [declaredType] = initializer.typeArguments?.params ?? [];
44773
- if (!declaredType || !isNodeOfType(declaredType, "TSUnionType")) return false;
44774
- let hasEmptySentinel = false;
44775
- let hasTruthyDomain = false;
44776
- for (const memberType of declaredType.types ?? []) {
44777
- if (isNodeOfType(memberType, "TSNullKeyword") || isNodeOfType(memberType, "TSUndefinedKeyword")) {
44778
- hasEmptySentinel = true;
44779
- continue;
44780
- }
44781
- if (!isClosedTruthyTypeDomain(memberType, initializationValue, scopes)) return false;
44782
- hasTruthyDomain = true;
44783
- }
44784
- return hasEmptySentinel && hasTruthyDomain;
44785
- };
44786
- const isSafeRefIdentifierUse = (identifier) => {
44787
- const expressionRoot = findTransparentExpressionRoot(identifier);
44788
- const parent = expressionRoot.parent;
44789
- if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.id === expressionRoot && parent.parent !== null && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const") return true;
44790
- if (parent && isNodeOfType(parent, "MemberExpression") && parent.object === expressionRoot && getStaticPropertyName(parent) === "current") return true;
44791
- if (!parent || !isNodeOfType(parent, "VariableDeclarator") || parent.init !== expressionRoot) return false;
44792
- return isNodeOfType(parent.id, "Identifier") && parent.parent !== null && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const";
44793
- };
44794
- const refDoesNotEscape = (branchRoot, refSymbol, scopes) => {
44795
- let didEscape = false;
44796
- walkAst(branchRoot, (child) => {
44797
- if (didEscape) return false;
44798
- if (!isNodeOfType(child, "Identifier")) return;
44799
- if (resolveConstIdentifierAlias(child, scopes)?.id !== refSymbol.id) return;
44800
- if (child === refSymbol.bindingIdentifier || isSafeRefIdentifierUse(child)) return;
44801
- didEscape = true;
44802
- return false;
44803
- });
44804
- return !didEscape;
44805
- };
44806
- const expressionContainsRefCurrent = (expression, refSymbol, scopes) => {
44807
- let didFindRefCurrent = false;
44808
- walkAst(expression, (child) => {
44809
- if (didFindRefCurrent) return false;
44810
- if (resolveReactRefSymbol(child, scopes)?.id !== refSymbol.id) return;
44811
- didFindRefCurrent = true;
44812
- return false;
44813
- });
44814
- return didFindRefCurrent;
44815
- };
44816
- const hasNoCompetingRefCurrentWrite = (branchRoot, assignmentExpression, refSymbol, scopes) => {
44817
- let writeCount = 0;
44818
- walkAst(branchRoot, (child) => {
44819
- if (writeCount > 1) return false;
44820
- if (isNodeOfType(child, "AssignmentExpression")) {
44821
- if (expressionContainsRefCurrent(child.left, refSymbol, scopes)) writeCount++;
44822
- return;
44823
- }
44824
- if (isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") {
44825
- if (expressionContainsRefCurrent(child.argument, refSymbol, scopes)) writeCount++;
44826
- return;
44827
- }
44828
- if (isNodeOfType(child, "ForInStatement") || isNodeOfType(child, "ForOfStatement")) {
44829
- if (expressionContainsRefCurrent(child.left, refSymbol, scopes)) writeCount++;
44830
- }
44831
- });
44832
- return writeCount === 1 && expressionContainsRefCurrent(assignmentExpression.left, refSymbol, scopes);
44833
- };
44834
44377
  const isEmptySentinel = (node, scopes) => isNodeOfType(node, "Literal") && node.value === null || isNodeOfType(node, "Identifier") && node.name === "undefined" && scopes.isGlobalReference(node);
44835
44378
  const hasRepeatedExecutionAncestor = (node, stop) => {
44836
44379
  let ancestor = node.parent;
@@ -44879,22 +44422,18 @@ const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol
44879
44422
  const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes) => {
44880
44423
  if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return true;
44881
44424
  if (assignmentExpression.operator !== "=") return false;
44882
- const renderOwner = findRenderPhaseComponentOrHook(assignmentExpression, scopes);
44883
- if (!renderOwner) return false;
44884
44425
  let descendant = assignmentExpression;
44885
44426
  let ancestor = descendant.parent;
44886
44427
  while (ancestor) {
44887
- const test = isNodeOfType(ancestor, "IfStatement") ? stripParenExpression(ancestor.test) : null;
44888
- if (isNodeOfType(ancestor, "IfStatement") && test && isNodeOfType(test, "UnaryExpression") && test.operator === "!" && isSameRefCurrentAlias(test.argument, refSymbol, scopes) && ancestor.consequent === descendant && isProvablyTruthyInitializationValue(assignmentExpression.right, scopes) && refHasClosedFalsySentinelDomain(refSymbol, assignmentExpression.right, scopes) && !hasRepeatedExecutionAncestor(assignmentExpression, ancestor.consequent) && !hasRepeatedExecutionAncestor(ancestor, renderOwner) && hasNoPriorCoExecutableWrite(assignmentExpression, ancestor.consequent, refSymbol, scopes) && hasNoCompetingRefCurrentWrite(renderOwner, assignmentExpression, refSymbol, scopes) && refDoesNotEscape(renderOwner, refSymbol, scopes)) return true;
44889
- if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(test, "BinaryExpression") && [
44428
+ if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(ancestor.test, "BinaryExpression") && [
44890
44429
  "===",
44891
44430
  "==",
44892
44431
  "!==",
44893
44432
  "!="
44894
- ].includes(test.operator)) {
44895
- const { left, right } = test;
44433
+ ].includes(ancestor.test.operator)) {
44434
+ const { left, right } = ancestor.test;
44896
44435
  const comparesEmptySentinel = isSameRefCurrentAlias(left, refSymbol, scopes) && isEmptySentinel(right, scopes) || isSameRefCurrentAlias(right, refSymbol, scopes) && isEmptySentinel(left, scopes);
44897
- const guardedBranch = test.operator === "===" || test.operator === "==" ? ancestor.consequent : ancestor.alternate;
44436
+ const guardedBranch = ancestor.test.operator === "===" || ancestor.test.operator === "==" ? ancestor.consequent : ancestor.alternate;
44898
44437
  if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch && !hasRepeatedExecutionAncestor(assignmentExpression, guardedBranch) && hasNoPriorCoExecutableWrite(assignmentExpression, guardedBranch, refSymbol, scopes)) return true;
44899
44438
  }
44900
44439
  descendant = ancestor;
@@ -52019,31 +51558,6 @@ const preferModuleScopePureFunction = defineRule({
52019
51558
  }
52020
51559
  });
52021
51560
  //#endregion
52022
- //#region src/plugin/utils/get-require-call-source.ts
52023
- const getRequireCallSource = (expression) => {
52024
- const unwrappedExpression = stripParenExpression(expression);
52025
- if (isNodeOfType(unwrappedExpression, "MemberExpression")) return getRequireCallSource(unwrappedExpression.object);
52026
- if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
52027
- if (!isNodeOfType(unwrappedExpression.callee, "Identifier") || unwrappedExpression.callee.name !== "require") return null;
52028
- const [firstArgument] = unwrappedExpression.arguments ?? [];
52029
- if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
52030
- return typeof firstArgument.value === "string" ? firstArgument.value : null;
52031
- };
52032
- //#endregion
52033
- //#region src/plugin/utils/is-proven-node-crypto-namespace-reference.ts
52034
- const NODE_CRYPTO_MODULE_SOURCES = new Set(["crypto", "node:crypto"]);
52035
- const isProvenNodeCryptoNamespaceReference = (expression, scopes) => {
52036
- const identifier = stripParenExpression(expression);
52037
- if (!isNodeOfType(identifier, "Identifier")) return false;
52038
- const symbol = resolveConstIdentifierAlias(identifier, scopes);
52039
- if (!symbol) return false;
52040
- if (symbol.kind === "import") {
52041
- const importBinding = getImportBindingForName(identifier, symbol.name);
52042
- return Boolean(importBinding && NODE_CRYPTO_MODULE_SOURCES.has(importBinding.source));
52043
- }
52044
- return Boolean(symbol.kind === "const" && symbol.initializer && NODE_CRYPTO_MODULE_SOURCES.has(getRequireCallSource(symbol.initializer) ?? ""));
52045
- };
52046
- //#endregion
52047
51561
  //#region src/plugin/rules/architecture/prefer-module-scope-static-value.ts
52048
51562
  const MUTATING_RECEIVER_METHOD_NAMES = new Set([...MUTATING_ARRAY_METHODS, ...MUTATING_COLLECTION_METHODS]);
52049
51563
  const isMutationContext = (referenceIdentifier) => {
@@ -52157,9 +51671,9 @@ const isImpureCall = (node, scopes) => {
52157
51671
  const callee = node.callee;
52158
51672
  if (isNodeOfType(callee, "Identifier")) return isImpureBareCallee(callee, scopes);
52159
51673
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return false;
51674
+ if (!isNodeOfType(callee.object, "Identifier")) return false;
52160
51675
  if (!isNodeOfType(callee.property, "Identifier")) return false;
52161
- for (const [receiverName, receiverMethodNames] of IMPURE_MEMBER_RECEIVERS) if (receiverMethodNames.has(callee.property.name) && (isProvenGlobalNamespaceReference(callee.object, receiverName, scopes) || receiverName === "crypto" && isProvenNodeCryptoNamespaceReference(callee.object, scopes))) return true;
52162
- return false;
51676
+ return Boolean(IMPURE_MEMBER_RECEIVERS.get(callee.object.name)?.has(callee.property.name));
52163
51677
  };
52164
51678
  const containsImpureExpression = (expression, scopes) => {
52165
51679
  let foundImpure = false;
@@ -56928,6 +56442,16 @@ const rnListCallbackPerRow = defineRule({
56928
56442
  }
56929
56443
  });
56930
56444
  //#endregion
56445
+ //#region src/plugin/utils/get-require-call-source.ts
56446
+ const getRequireCallSource = (expression) => {
56447
+ if (isNodeOfType(expression, "MemberExpression")) return getRequireCallSource(expression.object);
56448
+ if (!isNodeOfType(expression, "CallExpression")) return null;
56449
+ if (!isNodeOfType(expression.callee, "Identifier") || expression.callee.name !== "require") return null;
56450
+ const [firstArgument] = expression.arguments ?? [];
56451
+ if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
56452
+ return typeof firstArgument.value === "string" ? firstArgument.value : null;
56453
+ };
56454
+ //#endregion
56931
56455
  //#region src/plugin/utils/get-initializer-module-source.ts
56932
56456
  const getInitializerModuleSource = (contextNode, initializer) => {
56933
56457
  const requireSource = getRequireCallSource(initializer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.9-dev.c7f61cd",
3
+ "version": "0.7.9-dev.d7d38d7",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",