oxlint-plugin-react-doctor 0.7.9-dev.b392093 → 0.7.9-dev.b51022f

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 +181 -41
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6736,6 +6736,28 @@ const asyncParallel = defineRule({
6736
6736
  }
6737
6737
  });
6738
6738
  //#endregion
6739
+ //#region src/plugin/utils/collect-function-return-statements.ts
6740
+ const collectFunctionReturnStatements = (functionNode) => {
6741
+ if (!isFunctionLike$1(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return [];
6742
+ const returnStatements = [];
6743
+ walkAst(functionNode.body, (node) => {
6744
+ if (node !== functionNode.body && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
6745
+ if (isNodeOfType(node, "ReturnStatement")) returnStatements.push(node);
6746
+ });
6747
+ return returnStatements;
6748
+ };
6749
+ //#endregion
6750
+ //#region src/plugin/utils/is-nullish-expression.ts
6751
+ const isNullishExpression = (expression) => isNodeOfType(expression, "Literal") && expression.value === null || isNodeOfType(expression, "Identifier") && expression.name === "undefined" || isNodeOfType(expression, "UnaryExpression") && expression.operator === "void";
6752
+ //#endregion
6753
+ //#region src/plugin/utils/strip-this-parameter.ts
6754
+ const stripThisParameter = (parameters) => {
6755
+ const firstParameter = parameters[0];
6756
+ if (!firstParameter) return parameters;
6757
+ if (isNodeOfType(firstParameter, "Identifier") && firstParameter.name === "this") return parameters.slice(1);
6758
+ return parameters;
6759
+ };
6760
+ //#endregion
6739
6761
  //#region src/plugin/rules/security/auth-token-in-web-storage.ts
6740
6762
  const MESSAGE$60 = "Storing an auth token in `localStorage`/`sessionStorage` exposes it to any XSS on the page: JavaScript can read web storage and exfiltrate the token. Keep tokens in an `HttpOnly`, `Secure`, `SameSite` cookie instead.";
6741
6763
  const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
@@ -6747,12 +6769,8 @@ const STORAGE_GLOBALS = new Set([
6747
6769
  const SENSITIVE_KEY_PATTERN = /token|jwt|secret|password|passwd|credential|api[-_]?key|bearer|private[-_]?key/i;
6748
6770
  const NON_AUTH_TOKEN_PATTERN = /csrf|xsrf|device|fcm|apns|push|design|tokeniz|syntax|css|theme|color/i;
6749
6771
  const STRONG_AUTH_KEY_PATTERN = /jwt|secret|password|passwd|credential|private[-_]?key|api[-_]?key|bearer|access[-_]?token|refresh[-_]?token|auth[-_]?token|id[-_]?token|session/i;
6750
- const PRODUCT_API_KEY_RECORDS_PATTERN = /(?:^|[._:-])(?:created|saved|integration|mailing)[-_]?api[-_]?keys$/i;
6751
- const PRODUCT_API_KEY_COLLECTION_PATTERN = /[._:-](?:created|generated|saved)[-_]?api[-_]?keys$/i;
6752
6772
  const isAuthCredentialKey = (key) => {
6753
- if (PRODUCT_API_KEY_RECORDS_PATTERN.test(key)) return false;
6754
6773
  if (!SENSITIVE_KEY_PATTERN.test(key)) return false;
6755
- if (PRODUCT_API_KEY_COLLECTION_PATTERN.test(key)) return false;
6756
6774
  if (NON_AUTH_TOKEN_PATTERN.test(key) && !STRONG_AUTH_KEY_PATTERN.test(key)) return false;
6757
6775
  return true;
6758
6776
  };
@@ -6761,11 +6779,63 @@ const isDirectWebStorageObject = (node) => {
6761
6779
  if (isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.object, "Identifier") && STORAGE_GLOBALS.has(node.object.name) && isNodeOfType(node.property, "Identifier")) return STORAGE_NAMES.has(node.property.name);
6762
6780
  return false;
6763
6781
  };
6764
- const isWebStorageObject = (node) => {
6765
- if (isDirectWebStorageObject(node)) return true;
6766
- if (!isNodeOfType(node, "Identifier")) return false;
6767
- const binding = findVariableInitializer(node, node.name);
6768
- return binding?.initializer ? isDirectWebStorageObject(binding.initializer) : false;
6782
+ const immutableInitializer = (identifier, visitedIdentifiers = /* @__PURE__ */ new Set()) => {
6783
+ if (visitedIdentifiers.has(identifier)) return null;
6784
+ visitedIdentifiers.add(identifier);
6785
+ const binding = findVariableInitializer(identifier, identifier.name);
6786
+ if (!binding?.initializer) return null;
6787
+ if (isNodeOfType(binding.initializer, "FunctionDeclaration")) return binding.initializer;
6788
+ const declarator = binding.bindingIdentifier.parent;
6789
+ if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return null;
6790
+ const declaration = declarator.parent;
6791
+ if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) return null;
6792
+ if (declaration.kind !== "const") return null;
6793
+ const initializer = stripParenExpression(binding.initializer);
6794
+ if (!isNodeOfType(initializer, "Identifier")) return initializer;
6795
+ return immutableInitializer(initializer, visitedIdentifiers) ?? initializer;
6796
+ };
6797
+ const isWebStorageFactoryResult = (node, visitedNodes) => {
6798
+ const expression = stripParenExpression(node);
6799
+ if (isWebStorageObject(expression, new Set(visitedNodes))) return true;
6800
+ if (isNodeOfType(expression, "ConditionalExpression")) {
6801
+ const consequent = stripParenExpression(expression.consequent);
6802
+ const alternate = stripParenExpression(expression.alternate);
6803
+ return (isNullishExpression(consequent) || isWebStorageFactoryResult(consequent, visitedNodes)) && (isNullishExpression(alternate) || isWebStorageFactoryResult(alternate, visitedNodes)) && (!isNullishExpression(consequent) || !isNullishExpression(alternate));
6804
+ }
6805
+ if (isNodeOfType(expression, "LogicalExpression")) {
6806
+ if (expression.operator === "&&") return isWebStorageFactoryResult(expression.right, visitedNodes);
6807
+ const left = stripParenExpression(expression.left);
6808
+ const right = stripParenExpression(expression.right);
6809
+ const isLeftStorage = isWebStorageFactoryResult(left, visitedNodes);
6810
+ const isRightStorage = isWebStorageFactoryResult(right, visitedNodes);
6811
+ return (isLeftStorage || isNullishExpression(left)) && (isRightStorage || isNullishExpression(right)) && (isLeftStorage || isRightStorage);
6812
+ }
6813
+ return false;
6814
+ };
6815
+ const isWebStorageObject = (node, visitedNodes = /* @__PURE__ */ new Set()) => {
6816
+ const expression = stripParenExpression(node);
6817
+ if (visitedNodes.has(expression)) return false;
6818
+ visitedNodes.add(expression);
6819
+ if (isDirectWebStorageObject(expression)) return true;
6820
+ if (isNodeOfType(expression, "Identifier")) {
6821
+ const initializer = immutableInitializer(expression);
6822
+ return initializer ? isWebStorageObject(initializer, new Set(visitedNodes)) : false;
6823
+ }
6824
+ if (!isNodeOfType(expression, "CallExpression")) return false;
6825
+ const callee = stripParenExpression(expression.callee);
6826
+ if (!isNodeOfType(callee, "Identifier")) return false;
6827
+ const factory = immutableInitializer(callee);
6828
+ if (!isFunctionLike$1(factory)) return false;
6829
+ if (isNodeOfType(factory, "ArrowFunctionExpression") && !isNodeOfType(factory.body, "BlockStatement")) return isWebStorageFactoryResult(factory.body, visitedNodes);
6830
+ let didReturnWebStorage = false;
6831
+ for (const returnStatement of collectFunctionReturnStatements(factory)) {
6832
+ if (!returnStatement.argument) continue;
6833
+ const strippedReturn = stripParenExpression(returnStatement.argument);
6834
+ if (isNullishExpression(strippedReturn)) continue;
6835
+ if (!isWebStorageFactoryResult(strippedReturn, visitedNodes)) return false;
6836
+ didReturnWebStorage = true;
6837
+ }
6838
+ return didReturnWebStorage;
6769
6839
  };
6770
6840
  const resolveStaticKeyString = (node) => {
6771
6841
  if (isNodeOfType(node, "Literal") && typeof node.value === "string") return node.value;
@@ -6785,6 +6855,56 @@ const staticMemberName = (member) => {
6785
6855
  if (member.computed && isNodeOfType(member.property, "Literal") && typeof member.property.value === "string") return member.property.value;
6786
6856
  return null;
6787
6857
  };
6858
+ const parameterIndex = (expression, parameterSymbolIds, scopes, canUnwrapSerialization, visitedNodes = /* @__PURE__ */ new Set()) => {
6859
+ const strippedExpression = stripParenExpression(expression);
6860
+ if (visitedNodes.has(strippedExpression)) return null;
6861
+ visitedNodes.add(strippedExpression);
6862
+ if (isNodeOfType(strippedExpression, "Identifier")) {
6863
+ const directSymbolId = scopes.symbolFor(strippedExpression)?.id;
6864
+ const directIndex = directSymbolId === void 0 ? -1 : parameterSymbolIds.indexOf(directSymbolId);
6865
+ if (directIndex !== -1) return directIndex;
6866
+ const initializer = immutableInitializer(strippedExpression);
6867
+ return initializer ? parameterIndex(initializer, parameterSymbolIds, scopes, canUnwrapSerialization, visitedNodes) : null;
6868
+ }
6869
+ if (canUnwrapSerialization && isNodeOfType(strippedExpression, "CallExpression") && isNodeOfType(strippedExpression.callee, "MemberExpression") && !strippedExpression.callee.computed && isNodeOfType(strippedExpression.callee.object, "Identifier") && strippedExpression.callee.object.name === "JSON" && isNodeOfType(strippedExpression.callee.property, "Identifier") && strippedExpression.callee.property.name === "stringify") {
6870
+ const serializedArgument = strippedExpression.arguments[0];
6871
+ return serializedArgument ? parameterIndex(serializedArgument, parameterSymbolIds, scopes, true, visitedNodes) : null;
6872
+ }
6873
+ return null;
6874
+ };
6875
+ const storageHelperSinkCache = /* @__PURE__ */ new WeakMap();
6876
+ const findStorageHelperSinks = (functionNode, scopes) => {
6877
+ const cachedSinks = storageHelperSinkCache.get(functionNode);
6878
+ if (cachedSinks) return cachedSinks;
6879
+ if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) {
6880
+ storageHelperSinkCache.set(functionNode, []);
6881
+ return [];
6882
+ }
6883
+ const parameterSymbolIds = stripThisParameter(functionNode.params).map((parameter) => {
6884
+ const strippedParameter = stripParenExpression(parameter);
6885
+ const identifier = isNodeOfType(strippedParameter, "Identifier") ? strippedParameter : isNodeOfType(strippedParameter, "AssignmentPattern") && isNodeOfType(strippedParameter.left, "Identifier") ? strippedParameter.left : null;
6886
+ return identifier ? scopes.symbolFor(identifier)?.id ?? null : null;
6887
+ });
6888
+ const helperSinks = [];
6889
+ walkAst(functionNode.body, (child) => {
6890
+ if (child !== functionNode.body && isFunctionLike$1(child)) return false;
6891
+ if (!isNodeOfType(child, "CallExpression")) return;
6892
+ const callee = stripParenExpression(child.callee);
6893
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "setItem" || !isWebStorageObject(callee.object)) return;
6894
+ const keyExpression = child.arguments[0];
6895
+ const valueExpression = child.arguments[1];
6896
+ if (!keyExpression || !valueExpression) return;
6897
+ const keyParameterIndex = parameterIndex(keyExpression, parameterSymbolIds, scopes, false);
6898
+ const valueParameterIndex = parameterIndex(valueExpression, parameterSymbolIds, scopes, true);
6899
+ if (keyParameterIndex === null || valueParameterIndex === null) return;
6900
+ helperSinks.push({
6901
+ keyParameterIndex,
6902
+ valueParameterIndex
6903
+ });
6904
+ });
6905
+ storageHelperSinkCache.set(functionNode, helperSinks);
6906
+ return helperSinks;
6907
+ };
6788
6908
  const authTokenInWebStorage = defineRule({
6789
6909
  id: "auth-token-in-web-storage",
6790
6910
  title: "Auth token in web storage",
@@ -6792,14 +6912,23 @@ const authTokenInWebStorage = defineRule({
6792
6912
  recommendation: "Don't persist auth tokens (JWTs, access/refresh tokens, secrets) in `localStorage`/`sessionStorage`; they're readable by any XSS. Use an `HttpOnly` cookie set by the server.",
6793
6913
  create: skipNonProductionFiles((context) => ({
6794
6914
  CallExpression(node) {
6795
- const callee = node.callee;
6796
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
6797
- if (!isNodeOfType(callee.property, "Identifier") || callee.property.name !== "setItem") return;
6798
- if (!isWebStorageObject(stripParenExpression(callee.object))) return;
6799
- const keyArgument = node.arguments?.[0];
6800
- if (!keyArgument) return;
6801
- const keyString = resolveStaticKeyString(keyArgument);
6802
- if (keyString === null || !isAuthCredentialKey(keyString)) return;
6915
+ const callee = stripParenExpression(node.callee);
6916
+ const keyArguments = [];
6917
+ if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "setItem" && isWebStorageObject(stripParenExpression(callee.object))) {
6918
+ const keyArgument = node.arguments[0];
6919
+ if (keyArgument) keyArguments.push(keyArgument);
6920
+ } else if (isNodeOfType(callee, "Identifier")) {
6921
+ const helperFunction = immutableInitializer(callee);
6922
+ const helperSinks = helperFunction ? findStorageHelperSinks(helperFunction, context.scopes) : [];
6923
+ for (const helperSink of helperSinks) {
6924
+ const keyArgument = node.arguments[helperSink.keyParameterIndex];
6925
+ if (keyArgument && node.arguments[helperSink.valueParameterIndex]) keyArguments.push(keyArgument);
6926
+ }
6927
+ }
6928
+ if (!keyArguments.some((keyArgument) => {
6929
+ const keyString = resolveStaticKeyString(keyArgument);
6930
+ return keyString !== null && isAuthCredentialKey(keyString);
6931
+ })) return;
6803
6932
  context.report({
6804
6933
  node,
6805
6934
  message: MESSAGE$60
@@ -7047,9 +7176,6 @@ const isCreateElementCall = (node) => {
7047
7176
  return false;
7048
7177
  };
7049
7178
  //#endregion
7050
- //#region src/plugin/utils/is-nullish-expression.ts
7051
- const isNullishExpression = (expression) => isNodeOfType(expression, "Literal") && expression.value === null || isNodeOfType(expression, "Identifier") && expression.name === "undefined" || isNodeOfType(expression, "UnaryExpression") && expression.operator === "void";
7052
- //#endregion
7053
7179
  //#region src/plugin/rules/react-builtins/button-has-type.ts
7054
7180
  const MISSING_MESSAGE$2 = "Your users can submit the form by accident because a `<button>` with no `type` defaults to submit.";
7055
7181
  const INVALID_MESSAGE = "This button has an invalid `type`, so the browser may treat it like a submit button.";
@@ -8566,17 +8692,6 @@ const createMethodMutationAnalysis = (context) => {
8566
8692
  //#region src/plugin/utils/is-member-property.ts
8567
8693
  const isMemberProperty = (node, propertyName) => Boolean(node && isNodeOfType(node, "MemberExpression") && isNodeOfType(node.property, "Identifier") && node.property.name === propertyName);
8568
8694
  //#endregion
8569
- //#region src/plugin/utils/collect-function-return-statements.ts
8570
- const collectFunctionReturnStatements = (functionNode) => {
8571
- if (!isFunctionLike$1(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return [];
8572
- const returnStatements = [];
8573
- walkAst(functionNode.body, (node) => {
8574
- if (node !== functionNode.body && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
8575
- if (isNodeOfType(node, "ReturnStatement")) returnStatements.push(node);
8576
- });
8577
- return returnStatements;
8578
- };
8579
- //#endregion
8580
8695
  //#region src/plugin/utils/statement-always-exits.ts
8581
8696
  const statementAlwaysExits = (statement) => {
8582
8697
  if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement")) return true;
@@ -14751,6 +14866,29 @@ const getReleaseVerbName = (node) => {
14751
14866
  }
14752
14867
  return null;
14753
14868
  };
14869
+ const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) => {
14870
+ const releaseFunction = findEnclosingFunction$1(releaseReceiver);
14871
+ const usageFunction = findEnclosingFunction$1(usage.node);
14872
+ if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction) || !hasReactRefCurrentOrigin(releaseReceiver, context.scopes)) return false;
14873
+ const controllerKey = getListenerAbortControllerKey(usage, context);
14874
+ const refCurrentKey = resolveExpressionKey(releaseReceiver, context);
14875
+ if (controllerKey === null || refCurrentKey === null) return false;
14876
+ const usageFunctionBody = usageFunction.body;
14877
+ const previousAbortCalls = [];
14878
+ const ownershipAssignments = [];
14879
+ walkAst(usageFunctionBody, (child) => {
14880
+ if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
14881
+ if (isNodeOfType(child, "AssignmentExpression") && resolveExpressionKey(child.left, context) === refCurrentKey && resolveExpressionKey(child.right, context) === controllerKey) {
14882
+ ownershipAssignments.push(child);
14883
+ return;
14884
+ }
14885
+ if (!isNodeOfType(child, "CallExpression")) return;
14886
+ const childCallee = isNodeOfType(child.callee, "ChainExpression") ? child.callee.expression : stripParenExpression(child.callee);
14887
+ if (isNodeOfType(childCallee, "MemberExpression") && !childCallee.computed && isNodeOfType(childCallee.property, "Identifier") && childCallee.property.name === "abort" && resolveExpressionKey(childCallee.object, context) === refCurrentKey) previousAbortCalls.push(child);
14888
+ });
14889
+ const safeOwnershipAssignments = ownershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, previousAbortCalls, usageFunction, context));
14890
+ return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
14891
+ };
14754
14892
  const doesReleaseCallMatchUsage = (node, usage, context) => {
14755
14893
  const callNode = isNodeOfType(node, "ChainExpression") ? node.expression : node;
14756
14894
  if (!isNodeOfType(callNode, "CallExpression")) return false;
@@ -14770,6 +14908,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
14770
14908
  if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
14771
14909
  if (usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub" || releaseVerbName === "close" || releaseVerbName === "unwatch" || releaseVerbName === "unlisten" || BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(releaseVerbName))) return true;
14772
14910
  if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
14911
+ if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
14773
14912
  if (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey) return false;
14774
14913
  const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
14775
14914
  if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
@@ -14803,9 +14942,11 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
14803
14942
  return isNodeOfType(handlerArgument, "Literal") && handlerArgument.value === null;
14804
14943
  }
14805
14944
  if (releaseVerbName === "removeEventListener" || releaseVerbName === "removeListener" || releaseVerbName === "off") {
14806
- const releaseHandler = callNode.arguments?.[1];
14945
+ const usesUnaryListenerSignature = usage.registrationVerbName === "addListener" && isNodeOfType(usage.node, "CallExpression") && usage.node.arguments?.length === 1 && callNode.arguments?.length === 1;
14946
+ const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
14807
14947
  if (!releaseHandler) return releaseVerbName === "off";
14808
- return usage.handlerKey !== null && resolveExpressionKey(releaseHandler, context) === usage.handlerKey;
14948
+ const expectedHandlerKey = usesUnaryListenerSignature ? usage.eventKey : usage.handlerKey;
14949
+ return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey;
14809
14950
  }
14810
14951
  if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
14811
14952
  return true;
@@ -14818,8 +14959,7 @@ const isReturnedEffectCleanupFunction = (functionNode) => {
14818
14959
  currentNode = parentNode;
14819
14960
  parentNode = currentNode.parent;
14820
14961
  }
14821
- if (!isNodeOfType(parentNode, "ReturnStatement") || parentNode.argument !== currentNode) return false;
14822
- const effectCallback = findEnclosingFunction$1(parentNode);
14962
+ const effectCallback = isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode ? findEnclosingFunction$1(parentNode) : isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode ? parentNode : null;
14823
14963
  const effectCall = effectCallback?.parent;
14824
14964
  return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
14825
14965
  };
@@ -15105,6 +15245,11 @@ const doesResourceResultEscape = (resourceNode, allowReturnedResourceEscape, all
15105
15245
  parentNode = currentNode.parent;
15106
15246
  continue;
15107
15247
  }
15248
+ if (isNodeOfType(parentNode, "ConditionalExpression") && (parentNode.consequent === currentNode || parentNode.alternate === currentNode) || isNodeOfType(parentNode, "LogicalExpression") && (parentNode.right === currentNode || parentNode.left === currentNode && parentNode.operator !== "&&")) {
15249
+ currentNode = parentNode;
15250
+ parentNode = currentNode.parent;
15251
+ continue;
15252
+ }
15108
15253
  if (isNodeOfType(parentNode, "VariableDeclarator") && parentNode.init === currentNode && isNodeOfType(parentNode.id, "Identifier") && isNodeOfType(parentNode.parent, "VariableDeclaration") && parentNode.parent.kind === "const") {
15109
15254
  const ownerFunction = findEnclosingFunction$1(resourceNode);
15110
15255
  const resourceSymbol = context.scopes.symbolFor(parentNode.id);
@@ -34171,6 +34316,7 @@ const noChainStateUpdates = defineRule({
34171
34316
  id: "no-chain-state-updates",
34172
34317
  title: "State updates chained through effects",
34173
34318
  severity: "warn",
34319
+ disabledWhen: ["react:18"],
34174
34320
  tags: ["test-noise"],
34175
34321
  recommendation: "Set all the related state together in the event handler that starts it, instead of having one useEffect react to a state change and set more state. See https://react.dev/learn/you-might-not-need-an-effect#chains-of-computations",
34176
34322
  create: (context) => ({ CallExpression(node) {
@@ -53124,12 +53270,6 @@ const isInsideEs6Component$1 = (methodDefinition) => {
53124
53270
  if (!owningClass) return false;
53125
53271
  return isPreactOrReactComponentClass(owningClass);
53126
53272
  };
53127
- const stripThisParameter = (params) => {
53128
- const first = params[0];
53129
- if (!first) return params;
53130
- if (isNodeOfType(first, "Identifier") && first.name === "this") return params.slice(1);
53131
- return params;
53132
- };
53133
53273
  const preactNoRenderArguments = defineRule({
53134
53274
  id: "preact-no-render-arguments",
53135
53275
  title: "render() reads props from arguments",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.9-dev.b392093",
3
+ "version": "0.7.9-dev.b51022f",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",