oxlint-plugin-react-doctor 0.7.2-dev.6ae8e46 → 0.7.2-dev.82e0475

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2277,7 +2277,6 @@ const resolveStaticStringValues = (rawExpression, scopes, remainingHops) => {
2277
2277
  if (remainingHops === 0) return null;
2278
2278
  const symbol = scopes.referenceFor(expression)?.resolvedSymbol;
2279
2279
  if (!symbol || symbol.kind !== "const" || !symbol.initializer) return null;
2280
- if (!isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
2281
2280
  return resolveStaticStringValues(symbol.initializer, scopes, remainingHops - 1);
2282
2281
  }
2283
2282
  return null;
@@ -2317,8 +2316,7 @@ const isDirectChildOfLinkComponent = (openingElement) => {
2317
2316
  const isKeyboardOperableWidgetAnchor = (openingElement) => Boolean(hasJsxPropIgnoreCase(openingElement.attributes, "role")) && Boolean(hasJsxPropIgnoreCase(openingElement.attributes, "tabindex")) && (Boolean(hasJsxPropIgnoreCase(openingElement.attributes, "onkeydown")) || Boolean(hasJsxPropIgnoreCase(openingElement.attributes, "onkeyup")));
2318
2317
  const isInvalidHref = (value, validHrefs) => {
2319
2318
  if (validHrefs.has(value)) return false;
2320
- const withoutLeadingNonWord = value.replace(/^[^a-zA-Z0-9_]+/, "");
2321
- return value === "" || value === "#" || withoutLeadingNonWord.startsWith("javascript:");
2319
+ return value === "" || value === "#" || value === "javascript:void(0)";
2322
2320
  };
2323
2321
  const isNullishOrFragmentHref = (value) => {
2324
2322
  if (isNodeOfType(value, "JSXExpressionContainer")) {
@@ -5193,7 +5191,7 @@ const authTokenInWebStorage = defineRule({
5193
5191
  const callee = node.callee;
5194
5192
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
5195
5193
  if (!isNodeOfType(callee.property, "Identifier") || callee.property.name !== "setItem") return;
5196
- if (!isWebStorageObject(stripParenExpression(callee.object))) return;
5194
+ if (!isWebStorageObject(callee.object)) return;
5197
5195
  const keyArgument = node.arguments?.[0];
5198
5196
  if (!keyArgument) return;
5199
5197
  const keyString = resolveStaticKeyString(keyArgument);
@@ -6133,21 +6131,19 @@ const clickjackingRedirectRisk = defineRule({
6133
6131
  })
6134
6132
  });
6135
6133
  //#endregion
6136
- //#region src/plugin/utils/is-global-method-call.ts
6137
- const isGlobalMethodCall = (node, objectName, methodName) => {
6138
- if (!isNodeOfType(node, "CallExpression")) return false;
6139
- const callee = stripParenExpression(node.callee);
6140
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return false;
6141
- const receiver = stripParenExpression(callee.object);
6142
- return isNodeOfType(receiver, "Identifier") && receiver.name === objectName && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
6143
- };
6144
- //#endregion
6145
6134
  //#region src/plugin/rules/client/client-localstorage-no-version.ts
6146
6135
  const VERSIONED_KEY_PATTERN = /(?:[._:-]v\d+|@\d+|\bv\d+\b)/i;
6147
6136
  const CAMEL_CASE_VERSIONED_KEY_PATTERN = /[a-z]V\d+/;
6148
6137
  const STORAGE_OBJECTS = new Set(["localStorage", "sessionStorage"]);
6149
6138
  const isVersionedKey = (key) => VERSIONED_KEY_PATTERN.test(key) || CAMEL_CASE_VERSIONED_KEY_PATTERN.test(key);
6150
- const isJsonStringifyCall = (node) => isGlobalMethodCall(node, "JSON", "stringify");
6139
+ const isJsonStringifyCall = (node) => {
6140
+ if (!isNodeOfType(node, "CallExpression")) return false;
6141
+ if (!isNodeOfType(node.callee, "MemberExpression")) return false;
6142
+ if (!isNodeOfType(node.callee.object, "Identifier")) return false;
6143
+ if (node.callee.object.name !== "JSON") return false;
6144
+ if (!isNodeOfType(node.callee.property, "Identifier")) return false;
6145
+ return node.callee.property.name === "stringify";
6146
+ };
6151
6147
  const resolveStringKey = (keyArg, context) => {
6152
6148
  if (isNodeOfType(keyArg, "Literal")) return typeof keyArg.value === "string" ? keyArg.value : null;
6153
6149
  if (!isNodeOfType(keyArg, "Identifier")) return null;
@@ -6166,9 +6162,8 @@ const clientLocalstorageNoVersion = defineRule({
6166
6162
  recommendation: "Put a version in the storage key (e.g. \"myKey:v1\"). If you change the data shape later, old saved data can be ignored instead of crashing the app.",
6167
6163
  create: (context) => ({ CallExpression(node) {
6168
6164
  if (!isNodeOfType(node.callee, "MemberExpression")) return;
6169
- const receiver = stripParenExpression(node.callee.object);
6170
- if (!isNodeOfType(receiver, "Identifier")) return;
6171
- if (!STORAGE_OBJECTS.has(receiver.name)) return;
6165
+ if (!isNodeOfType(node.callee.object, "Identifier")) return;
6166
+ if (!STORAGE_OBJECTS.has(node.callee.object.name)) return;
6172
6167
  if (!isNodeOfType(node.callee.property, "Identifier")) return;
6173
6168
  if (node.callee.property.name !== "setItem") return;
6174
6169
  const keyArg = node.arguments?.[0];
@@ -6181,7 +6176,7 @@ const clientLocalstorageNoVersion = defineRule({
6181
6176
  if (!isJsonStringifyCall(valueArg)) return;
6182
6177
  context.report({
6183
6178
  node: keyArg,
6184
- message: `${receiver.name}.setItem("${keyValue}", JSON.stringify(...)) has no version, so changing the data shape later crashes your users' saved sessions. Add one to the key (e.g. "${keyValue}:v1").`
6179
+ message: `${node.callee.object.name}.setItem("${keyValue}", JSON.stringify(...)) has no version, so changing the data shape later crashes your users' saved sessions. Add one to the key (e.g. "${keyValue}:v1").`
6185
6180
  });
6186
6181
  } })
6187
6182
  });
@@ -7723,7 +7718,6 @@ const isResultDiscardedCall = (callExpression) => {
7723
7718
  let parent = node.parent;
7724
7719
  while (parent) {
7725
7720
  if (isNodeOfType(parent, "ExpressionStatement")) return true;
7726
- if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "void") return true;
7727
7721
  if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === node) return true;
7728
7722
  if (isNodeOfType(parent, "ChainExpression")) {
7729
7723
  node = parent;
@@ -7958,13 +7952,6 @@ const collectCleanupBindings = (effectCallback) => {
7958
7952
  };
7959
7953
  if (!isNodeOfType(effectCallback, "ArrowFunctionExpression") && !isNodeOfType(effectCallback, "FunctionExpression")) return bindings;
7960
7954
  if (!isNodeOfType(effectCallback.body, "BlockStatement")) return bindings;
7961
- walkAst(effectCallback.body, (child) => {
7962
- if (!isSubscribeOrObserveCall(child)) return;
7963
- if (!isNodeOfType(child, "CallExpression")) return;
7964
- if (!isNodeOfType(child.callee, "MemberExpression")) return;
7965
- if (!isNodeOfType(child.callee.object, "Identifier")) return;
7966
- bindings.subscriptionNames.add(child.callee.object.name);
7967
- });
7968
7955
  walkInsideStatementBlocks(effectCallback.body, (child) => {
7969
7956
  if (!isNodeOfType(child, "VariableDeclaration")) return;
7970
7957
  for (const declarator of child.declarations ?? []) {
@@ -8007,22 +7994,6 @@ const getRangeStart = (node) => {
8007
7994
  const rangeStart = node.range?.[0];
8008
7995
  return typeof rangeStart === "number" ? rangeStart : null;
8009
7996
  };
8010
- const removeSynchronouslyReleasedUsages = (callback, usages) => {
8011
- if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
8012
- if (!isNodeOfType(callback.body, "BlockStatement")) return usages;
8013
- const releaseStarts = [];
8014
- walkInsideStatementBlocks(callback.body, (child) => {
8015
- if (!isReleaseLikeCall(child, EMPTY_NAME_SET$1, EMPTY_NAME_SET$1)) return;
8016
- const releaseStart = getRangeStart(child);
8017
- if (releaseStart !== null) releaseStarts.push(releaseStart);
8018
- });
8019
- if (releaseStarts.length === 0) return usages;
8020
- return usages.filter((usage) => {
8021
- const usageStart = getRangeStart(usage.node);
8022
- if (usageStart === null) return true;
8023
- return !releaseStarts.some((releaseStart) => releaseStart > usageStart);
8024
- });
8025
- };
8026
7997
  const cleanupReturnRunsAfterUsage = (returnStatement, usages) => {
8027
7998
  if (returnStatement.argument && isCleanupReturningSubscribeLikeCallExpression(returnStatement.argument)) return true;
8028
7999
  const returnStart = getRangeStart(returnStatement);
@@ -8046,14 +8017,7 @@ const effectHasCleanupReturn = (callback, usages) => {
8046
8017
  return didFindCleanupReturn;
8047
8018
  };
8048
8019
  const EMPTY_NAME_SET$1 = /* @__PURE__ */ new Set();
8049
- const isSelfReleasingListenerOptionProperty = (property) => {
8050
- if (!isNodeOfType(property, "Property")) return false;
8051
- const keyName = isNodeOfType(property.key, "Identifier") ? property.key.name : isNodeOfType(property.key, "Literal") ? property.key.value : null;
8052
- if (keyName === "signal") return true;
8053
- if (keyName !== "once") return false;
8054
- return isNodeOfType(property.value, "Literal") && property.value.value === true;
8055
- };
8056
- const hasSelfReleasingListenerOptions = (node) => isNodeOfType(node, "CallExpression") && (node.arguments ?? []).some((argument) => isNodeOfType(argument, "ObjectExpression") && (argument.properties ?? []).some(isSelfReleasingListenerOptionProperty));
8020
+ const hasSelfReleasingListenerOptions = (node) => isNodeOfType(node, "CallExpression") && (node.arguments ?? []).some((argument) => isNodeOfType(argument, "ObjectExpression") && (argument.properties ?? []).some((property) => isNodeOfType(property, "Property") && isNodeOfType(property.key, "Identifier") && (property.key.name === "signal" || property.key.name === "once" && isNodeOfType(property.value, "Literal") && property.value.value === true)));
8057
8021
  const PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB = new Map([
8058
8022
  ["addEventListener", new Set(["removeEventListener", "abort"])],
8059
8023
  ["addListener", new Set([
@@ -8128,12 +8092,10 @@ const findRetainedFunctionLeak = (retainedFunction) => {
8128
8092
  if (!isFunctionLike$1(retainedFunction)) return null;
8129
8093
  const body = retainedFunction.body;
8130
8094
  if (!body) return null;
8131
- const conciseReturnValue = isNodeOfType(body, "BlockStatement") ? null : body;
8132
8095
  let leak = null;
8133
8096
  walkAst(body, (child) => {
8134
8097
  if (leak !== null) return false;
8135
8098
  if (isFunctionLike$1(child)) return false;
8136
- if (child === conciseReturnValue && isNodeOfType(child, "CallExpression")) return;
8137
8099
  if (isSocketConstruction(child) && isResultDiscardedCall(child) && !bodyContainsPairedReleaseCall(body, SOCKET_RELEASE_VERB_NAMES)) {
8138
8100
  leak = {
8139
8101
  kind: "socket",
@@ -8195,7 +8157,7 @@ const effectNeedsCleanup = defineRule({
8195
8157
  if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
8196
8158
  const callback = getEffectCallback(node);
8197
8159
  if (!callback) return;
8198
- const usages = removeSynchronouslyReleasedUsages(callback, findSubscribeLikeUsages(callback));
8160
+ const usages = findSubscribeLikeUsages(callback);
8199
8161
  if (usages.length === 0) return;
8200
8162
  if (effectHasCleanupReturn(callback, usages)) return;
8201
8163
  const firstUsage = usages[0];
@@ -8980,24 +8942,6 @@ const isAstDescendant = (inner, outer) => {
8980
8942
  return false;
8981
8943
  };
8982
8944
  //#endregion
8983
- //#region src/plugin/utils/is-imported-from-non-react-module.ts
8984
- const isImportedFromNonReactModule = (contextNode, localIdentifierName) => {
8985
- const importSource = getImportSourceForName(contextNode, localIdentifierName);
8986
- if (importSource === null) return false;
8987
- return !REACT_RUNTIME_MODULE_SOURCES.has(importSource);
8988
- };
8989
- //#endregion
8990
- //#region src/plugin/utils/is-non-react-effect-event-callee.ts
8991
- const resolvesToLocalNonImportBinding = (identifier, scopes) => {
8992
- const symbol = scopes.referenceFor(identifier)?.resolvedSymbol;
8993
- return Boolean(symbol && symbol.kind !== "import");
8994
- };
8995
- const isNonReactEffectEventCallee = (callee, contextNode, scopes) => {
8996
- if (isNodeOfType(callee, "Identifier")) return isImportedFromNonReactModule(contextNode, callee.name) || resolvesToLocalNonImportBinding(callee, scopes);
8997
- if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.object, "Identifier")) return isImportedFromNonReactModule(contextNode, callee.object.name);
8998
- return false;
8999
- };
9000
- //#endregion
9001
8945
  //#region src/plugin/rules/react-builtins/exhaustive-deps-symbol-stability.ts
9002
8946
  /**
9003
8947
  * Symbol-stability helpers consumed by the `exhaustive-deps` rule.
@@ -9062,11 +9006,10 @@ const symbolHasStableHookOrigin = (symbol) => {
9062
9006
  }
9063
9007
  return false;
9064
9008
  };
9065
- const symbolHasUseEffectEventOrigin = (symbol, scopes) => {
9009
+ const symbolHasUseEffectEventOrigin = (symbol) => {
9066
9010
  const initializer = symbol.initializer ? unwrapExpression$3(symbol.initializer) : null;
9067
9011
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
9068
- if (getHookName(initializer.callee) !== "useEffectEvent") return false;
9069
- return !isNonReactEffectEventCallee(initializer.callee, initializer, scopes);
9012
+ return getHookName(initializer.callee) === "useEffectEvent";
9070
9013
  };
9071
9014
  const getFunctionValueNode = (symbol) => {
9072
9015
  if (symbol.kind === "function" && isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return symbol.declarationNode;
@@ -9803,7 +9746,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
9803
9746
  if (isLiteralOrEmptyTemplate(stripped)) continue;
9804
9747
  if (isNodeOfType(stripped, "Identifier")) {
9805
9748
  const depSymbol = context.scopes.symbolFor(stripped);
9806
- if (depSymbol && symbolHasUseEffectEventOrigin(depSymbol, context.scopes)) {
9749
+ if (depSymbol && symbolHasUseEffectEventOrigin(depSymbol)) {
9807
9750
  context.report({
9808
9751
  node: elementNode,
9809
9752
  message: buildEffectEventDepMessage()
@@ -10229,10 +10172,7 @@ const PRAGMA = "React";
10229
10172
  const isReactFunctionCall = (node, expectedCall) => {
10230
10173
  if (!isNodeOfType(node, "CallExpression")) return false;
10231
10174
  if (getCalleeName$2(node) !== expectedCall) return false;
10232
- if (isNodeOfType(node.callee, "MemberExpression")) {
10233
- const receiver = stripParenExpression(node.callee.object);
10234
- return isNodeOfType(receiver, "Identifier") && receiver.name === PRAGMA;
10235
- }
10175
+ if (isNodeOfType(node.callee, "MemberExpression")) return isNodeOfType(node.callee.object, "Identifier") && node.callee.object.name === PRAGMA;
10236
10176
  return true;
10237
10177
  };
10238
10178
  //#endregion
@@ -12309,15 +12249,14 @@ const jsCacheStorage = defineRule({
12309
12249
  "ArrowFunctionExpression:exit": exitFunctionScope,
12310
12250
  CallExpression(node) {
12311
12251
  if (!isMemberProperty(node.callee, "getItem")) return;
12312
- const receiver = stripParenExpression(node.callee.object);
12313
- if (!isNodeOfType(receiver, "Identifier") || !STORAGE_OBJECTS$1.has(receiver.name)) return;
12252
+ if (!isNodeOfType(node.callee.object, "Identifier") || !STORAGE_OBJECTS$1.has(node.callee.object.name)) return;
12314
12253
  if (!isNodeOfType(node.arguments?.[0], "Literal")) return;
12315
12254
  const storageReadCounts = storageReadCountStack[storageReadCountStack.length - 1];
12316
12255
  const storageKey = String(node.arguments[0].value);
12317
12256
  const readCount = (storageReadCounts.get(storageKey) ?? 0) + 1;
12318
12257
  storageReadCounts.set(storageKey, readCount);
12319
12258
  if (readCount === 2) {
12320
- const storageName = receiver.name;
12259
+ const storageName = node.callee.object.name;
12321
12260
  context.report({
12322
12261
  node,
12323
12262
  message: `This is slow because ${storageName}.getItem("${storageKey}") runs several times & re-parses the data each call, so read it once & reuse the value`
@@ -12331,16 +12270,13 @@ const jsCacheStorage = defineRule({
12331
12270
  //#region src/plugin/rules/js-performance/js-combine-iterations.ts
12332
12271
  const isIteratorProducingCall = (callExpression, generatorNamesInFile) => {
12333
12272
  const callee = callExpression.callee;
12334
- if (isNodeOfType(callee, "MemberExpression")) {
12335
- const receiver = stripParenExpression(callee.object);
12336
- if (isNodeOfType(receiver, "Identifier") && receiver.name === "Iterator" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "from") return true;
12337
- if (isNodeOfType(callee.property, "Identifier") && ITERATOR_PRODUCING_METHOD_NAMES.has(callee.property.name)) {
12338
- if (isNodeOfType(receiver, "Identifier") && receiver.name === "Object") return false;
12339
- return true;
12340
- }
12341
- return false;
12342
- }
12273
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Iterator" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "from") return true;
12343
12274
  if (isNodeOfType(callee, "Identifier") && generatorNamesInFile.has(callee.name)) return true;
12275
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && ITERATOR_PRODUCING_METHOD_NAMES.has(callee.property.name)) {
12276
+ const receiver = callee.object;
12277
+ if (isNodeOfType(receiver, "Identifier") && receiver.name === "Object") return false;
12278
+ return true;
12279
+ }
12344
12280
  return false;
12345
12281
  };
12346
12282
  const isChainPassThroughCall = (callExpression) => {
@@ -12352,7 +12288,10 @@ const isChainPassThroughCall = (callExpression) => {
12352
12288
  const isReceiverChainIteratorRooted = (receiverNode, generatorNamesInFile) => {
12353
12289
  let cursor = receiverNode;
12354
12290
  while (cursor) {
12355
- cursor = stripParenExpression(cursor);
12291
+ if (isNodeOfType(cursor, "ChainExpression")) {
12292
+ cursor = cursor.expression;
12293
+ continue;
12294
+ }
12356
12295
  if (!isNodeOfType(cursor, "CallExpression")) return false;
12357
12296
  if (isIteratorProducingCall(cursor, generatorNamesInFile)) return true;
12358
12297
  if (!isChainPassThroughCall(cursor)) return false;
@@ -12428,7 +12367,10 @@ const isStringSplitRootedChain = (receiverNode) => {
12428
12367
  let hops = 0;
12429
12368
  while (cursor && hops < 12) {
12430
12369
  hops += 1;
12431
- cursor = stripParenExpression(cursor);
12370
+ if (isNodeOfType(cursor, "ChainExpression")) {
12371
+ cursor = cursor.expression;
12372
+ continue;
12373
+ }
12432
12374
  if (!isNodeOfType(cursor, "CallExpression")) return false;
12433
12375
  const callee = cursor.callee;
12434
12376
  if (!isNodeOfType(callee, "MemberExpression")) return false;
@@ -12452,7 +12394,10 @@ const isSmallLiteralArray = (node) => {
12452
12394
  const isSmallLiteralArrayRootedChain = (receiverNode, smallConstArrayNames) => {
12453
12395
  let cursor = receiverNode;
12454
12396
  while (cursor) {
12455
- cursor = stripParenExpression(cursor);
12397
+ if (isNodeOfType(cursor, "ChainExpression")) {
12398
+ cursor = cursor.expression;
12399
+ continue;
12400
+ }
12456
12401
  if (isNodeOfType(cursor, "ArrayExpression")) return isSmallLiteralArray(cursor);
12457
12402
  if (isNodeOfType(cursor, "Identifier")) return smallConstArrayNames.has(cursor.name);
12458
12403
  if (!isNodeOfType(cursor, "CallExpression")) return false;
@@ -12517,7 +12462,7 @@ const jsCombineIterations = defineRule({
12517
12462
  if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier")) return;
12518
12463
  const outerMethod = node.callee.property.name;
12519
12464
  if (!CHAINABLE_ITERATION_METHODS.has(outerMethod)) return;
12520
- const innerCall = stripParenExpression(node.callee.object);
12465
+ const innerCall = node.callee.object;
12521
12466
  if (!isNodeOfType(innerCall, "CallExpression") || !isNodeOfType(innerCall.callee, "MemberExpression") || !isNodeOfType(innerCall.callee.property, "Identifier")) return;
12522
12467
  const innerMethod = innerCall.callee.property.name;
12523
12468
  if (!CHAINABLE_ITERATION_METHODS.has(innerMethod)) return;
@@ -12590,10 +12535,10 @@ const jsFlatmapFilter = defineRule({
12590
12535
  if (!filterArgument) return;
12591
12536
  const isIdentityArrow = isNodeOfType(filterArgument, "ArrowFunctionExpression") && filterArgument.params?.length === 1 && isNodeOfType(filterArgument.body, "Identifier") && isNodeOfType(filterArgument.params[0], "Identifier") && filterArgument.body.name === filterArgument.params[0].name;
12592
12537
  if (!(isNodeOfType(filterArgument, "Identifier") && filterArgument.name === "Boolean" || isIdentityArrow)) return;
12593
- const innerCall = stripParenExpression(node.callee.object);
12538
+ const innerCall = node.callee.object;
12594
12539
  if (!isNodeOfType(innerCall, "CallExpression") || !isNodeOfType(innerCall.callee, "MemberExpression") || !isNodeOfType(innerCall.callee.property, "Identifier")) return;
12595
12540
  if (innerCall.callee.property.name !== "map") return;
12596
- const receiver = stripParenExpression(innerCall.callee.object);
12541
+ const receiver = innerCall.callee.object;
12597
12542
  if (receiver && isNodeOfType(receiver, "ArrayExpression")) {
12598
12543
  const elements = receiver.elements ?? [];
12599
12544
  if (elements.length > 0 && elements.length <= 8 && elements.every((element) => element == null || !isNodeOfType(element, "SpreadElement"))) return;
@@ -13853,7 +13798,7 @@ const jsTosortedImmutable = defineRule({
13853
13798
  recommendation: "Use `array.toSorted()` (ES2023) instead of `[...array].sort()` so you sort without copying the array first",
13854
13799
  create: (context) => ({ CallExpression(node) {
13855
13800
  if (!isMemberProperty(node.callee, "sort")) return;
13856
- const receiver = stripParenExpression(node.callee.object);
13801
+ const receiver = node.callee.object;
13857
13802
  if (isNodeOfType(receiver, "ArrayExpression") && receiver.elements?.length === 1 && isNodeOfType(receiver.elements[0], "SpreadElement")) {
13858
13803
  const spreadArgument = receiver.elements[0].argument;
13859
13804
  if (isFreshOrIteratorAllocation(spreadArgument)) return;
@@ -18890,8 +18835,7 @@ const isInsidePollingLoop = (navigationNode, effectCallback, timerScheduledNames
18890
18835
  };
18891
18836
  const describeClientSideNavigation = (node) => {
18892
18837
  if (isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression")) {
18893
- const receiver = stripParenExpression(node.callee.object);
18894
- const objectName = isNodeOfType(receiver, "Identifier") ? receiver.name : null;
18838
+ const objectName = isNodeOfType(node.callee.object, "Identifier") ? node.callee.object.name : null;
18895
18839
  const methodName = isNodeOfType(node.callee.property, "Identifier") ? node.callee.property.name : null;
18896
18840
  if (objectName === "router" && (methodName === "push" || methodName === "replace")) return `router.${methodName}() in useEffect flashes the wrong page before redirecting.`;
18897
18841
  }
@@ -19511,7 +19455,7 @@ const getCookieMutationMethodName = (node, locallyScopedCookieBindings) => {
19511
19455
  if (!isNodeOfType(node.callee, "MemberExpression")) return null;
19512
19456
  if (!isNodeOfType(node.callee.property, "Identifier")) return null;
19513
19457
  if (!COOKIE_MUTATION_METHOD_NAMES.has(node.callee.property.name)) return null;
19514
- if (!isCookieReceiver(stripParenExpression(node.callee.object), locallyScopedCookieBindings)) return null;
19458
+ if (!isCookieReceiver(node.callee.object, locallyScopedCookieBindings)) return null;
19515
19459
  return node.callee.property.name;
19516
19460
  };
19517
19461
  const isMutatingFetchCall = (node) => {
@@ -19525,7 +19469,7 @@ const isMutatingDbCall = (node, locallyScopedSafeBindings) => {
19525
19469
  if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "MemberExpression")) return false;
19526
19470
  const { property, object } = node.callee;
19527
19471
  if (!isNodeOfType(property, "Identifier") || !MUTATION_METHOD_NAMES.has(property.name)) return false;
19528
- if (isSafeReceiverChain(stripParenExpression(object), locallyScopedSafeBindings)) return false;
19472
+ if (isSafeReceiverChain(object, locallyScopedSafeBindings)) return false;
19529
19473
  return true;
19530
19474
  };
19531
19475
  const getDbCallDescription = (node) => {
@@ -19533,8 +19477,7 @@ const getDbCallDescription = (node) => {
19533
19477
  if (!isNodeOfType(node.callee, "MemberExpression")) return ".unknown()";
19534
19478
  if (!isNodeOfType(node.callee.property, "Identifier")) return ".unknown()";
19535
19479
  const methodName = node.callee.property.name;
19536
- const receiver = stripParenExpression(node.callee.object);
19537
- const rootObjectName = isNodeOfType(receiver, "Identifier") ? receiver.name : null;
19480
+ const rootObjectName = isNodeOfType(node.callee.object, "Identifier") ? node.callee.object.name : null;
19538
19481
  return rootObjectName ? `${rootObjectName}.${methodName}()` : `.${methodName}()`;
19539
19482
  };
19540
19483
  const findSideEffect = (node, options = {}) => {
@@ -20934,13 +20877,13 @@ const getCallExpr = (ref, current = ref.identifier.parent) => {
20934
20877
  if (isNodeOfType(current, "CallExpression")) {
20935
20878
  let node = ref.identifier;
20936
20879
  let parent = node.parent;
20937
- while (parent && (isNodeOfType(parent, "MemberExpression") || TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type))) {
20880
+ while (parent && isNodeOfType(parent, "MemberExpression")) {
20938
20881
  node = parent;
20939
20882
  parent = node.parent;
20940
20883
  }
20941
20884
  if (current.callee === node) return current;
20942
20885
  }
20943
- if (isNodeOfType(current, "MemberExpression") || TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(current.type)) return getCallExpr(ref, current.parent);
20886
+ if (isNodeOfType(current, "MemberExpression")) return getCallExpr(ref, current.parent);
20944
20887
  return null;
20945
20888
  };
20946
20889
  const getArgsUpstreamRefs = (analysis, ref) => {
@@ -21075,7 +21018,7 @@ const isReactNamedImportReference = (ref, importedName) => Boolean(ref?.resolved
21075
21018
  const importDeclaration = declarationNode.parent;
21076
21019
  return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
21077
21020
  }));
21078
- const isHookCallee$1 = (analysis, node, hookName) => {
21021
+ const isHookCallee = (analysis, node, hookName) => {
21079
21022
  if (!node) return false;
21080
21023
  if (isNodeOfType(node, "Identifier")) {
21081
21024
  if (node.name === hookName) return true;
@@ -21084,19 +21027,15 @@ const isHookCallee$1 = (analysis, node, hookName) => {
21084
21027
  if (parent && isNodeOfType(parent, "MemberExpression") && isNodeOfType(parent.object, "Identifier") && parent.object.name === "React" && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
21085
21028
  return false;
21086
21029
  }
21087
- if (isNodeOfType(node, "MemberExpression")) {
21088
- const receiver = stripParenExpression(node.object);
21089
- return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
21090
- }
21030
+ if (isNodeOfType(node, "MemberExpression")) return isNodeOfType(node.object, "Identifier") && node.object.name === "React" && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
21091
21031
  return false;
21092
21032
  };
21093
21033
  const isUseEffect = (node) => {
21094
21034
  if (!node || !isNodeOfType(node, "CallExpression")) return false;
21095
21035
  const callee = node.callee;
21096
21036
  if (isNodeOfType(callee, "Identifier") && callee.name === "useEffect") return true;
21097
- if (!isNodeOfType(callee, "MemberExpression")) return false;
21098
- const receiver = stripParenExpression(callee.object);
21099
- return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useEffect";
21037
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "React" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useEffect") return true;
21038
+ return false;
21100
21039
  };
21101
21040
  const getEffectFn = (analysis, node) => {
21102
21041
  if (!isNodeOfType(node, "CallExpression")) return null;
@@ -21124,7 +21063,7 @@ const isState = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
21124
21063
  const node = def.node;
21125
21064
  if (!isNodeOfType(node, "VariableDeclarator")) return false;
21126
21065
  if (!isNodeOfType(node.init, "CallExpression")) return false;
21127
- if (!isHookCallee$1(analysis, node.init.callee, "useState")) return false;
21066
+ if (!isHookCallee(analysis, node.init.callee, "useState")) return false;
21128
21067
  if (!isNodeOfType(node.id, "ArrayPattern")) return false;
21129
21068
  const elements = node.id.elements ?? [];
21130
21069
  if (elements.length !== 1 && elements.length !== 2) return false;
@@ -21135,7 +21074,7 @@ const isStateSetter = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) =
21135
21074
  const node = def.node;
21136
21075
  if (!isNodeOfType(node, "VariableDeclarator")) return false;
21137
21076
  if (!isNodeOfType(node.init, "CallExpression")) return false;
21138
- if (!isHookCallee$1(analysis, node.init.callee, "useState")) return false;
21077
+ if (!isHookCallee(analysis, node.init.callee, "useState")) return false;
21139
21078
  if (!isNodeOfType(node.id, "ArrayPattern")) return false;
21140
21079
  const elements = node.id.elements ?? [];
21141
21080
  if (elements.length !== 2) return false;
@@ -21182,7 +21121,7 @@ const isRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
21182
21121
  const node = def.node;
21183
21122
  if (!isNodeOfType(node, "VariableDeclarator")) return false;
21184
21123
  if (!isNodeOfType(node.init, "CallExpression")) return false;
21185
- return isHookCallee$1(analysis, node.init.callee, "useRef");
21124
+ return isHookCallee(analysis, node.init.callee, "useRef");
21186
21125
  }));
21187
21126
  const isRefCurrent = (ref) => {
21188
21127
  const parent = ref.identifier.parent;
@@ -21195,15 +21134,11 @@ const isSyncStateSetterCall = (analysis, ref, effectFn) => isStateSetterCall(ana
21195
21134
  const HANDLER_NAMED_METHOD_PATTERN = /^(on|handle)[A-Z]/;
21196
21135
  const isPropCallbackInvocationRef = (analysis, ref) => {
21197
21136
  if (!isPropAlias(analysis, ref)) return false;
21198
- let effectiveNode = ref.identifier;
21199
- let parent = effectiveNode.parent;
21200
- while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === effectiveNode) {
21201
- effectiveNode = parent;
21202
- parent = effectiveNode.parent;
21203
- }
21137
+ const identifier = ref.identifier;
21138
+ const parent = identifier.parent;
21204
21139
  if (!parent) return false;
21205
- if (isNodeOfType(parent, "CallExpression") && parent.callee === effectiveNode) return true;
21206
- if (isNodeOfType(parent, "MemberExpression") && parent.object === effectiveNode) {
21140
+ if (isNodeOfType(parent, "CallExpression") && parent.callee === identifier) return true;
21141
+ if (isNodeOfType(parent, "MemberExpression") && parent.object === identifier) {
21207
21142
  const memberParent = parent.parent;
21208
21143
  if (isNodeOfType(memberParent, "CallExpression") && memberParent.callee === parent) {
21209
21144
  if (!parent.computed && isNodeOfType(parent.property, "Identifier") && HANDLER_NAMED_METHOD_PATTERN.test(parent.property.name)) return true;
@@ -21214,7 +21149,7 @@ const isPropCallbackInvocationRef = (analysis, ref) => {
21214
21149
  };
21215
21150
  const isRefCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isRefCurrent(innerRef) || isRef(analysis, innerRef));
21216
21151
  const getUseStateDecl = (analysis, ref) => {
21217
- let node = getUpstreamRefs(analysis, ref).find((upRef) => isHookCallee$1(analysis, upRef.identifier, "useState"))?.identifier;
21152
+ let node = getUpstreamRefs(analysis, ref).find((upRef) => isHookCallee(analysis, upRef.identifier, "useState"))?.identifier;
21218
21153
  while (node && !isNodeOfType(node, "VariableDeclarator")) node = node.parent;
21219
21154
  return node ?? null;
21220
21155
  };
@@ -21460,23 +21395,7 @@ const ALWAYS_FOCUSABLE_TAGS = new Set([
21460
21395
  "summary",
21461
21396
  "textarea"
21462
21397
  ]);
21463
- const isStaticallyFalseBooleanAttribute = (attribute) => {
21464
- const value = attribute.value;
21465
- if (!value || !isNodeOfType(value, "JSXExpressionContainer")) return false;
21466
- const expression = value.expression;
21467
- return isNodeOfType(expression, "Literal") && expression.value === false;
21468
- };
21469
- const DISABLEABLE_TAGS = new Set([
21470
- "button",
21471
- "input",
21472
- "select",
21473
- "textarea"
21474
- ]);
21475
21398
  const isNativelyFocusable = (tagName, openingElement) => {
21476
- if (DISABLEABLE_TAGS.has(tagName)) {
21477
- const disabledAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "disabled");
21478
- if (disabledAttribute && !isStaticallyFalseBooleanAttribute(disabledAttribute)) return false;
21479
- }
21480
21399
  if (ALWAYS_FOCUSABLE_TAGS.has(tagName)) return true;
21481
21400
  switch (tagName) {
21482
21401
  case "input": {
@@ -21490,10 +21409,7 @@ const isNativelyFocusable = (tagName, openingElement) => {
21490
21409
  case "a":
21491
21410
  case "area": return hasJsxPropIgnoreCase(openingElement.attributes, "href") !== void 0;
21492
21411
  case "audio":
21493
- case "video": {
21494
- const controlsAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "controls");
21495
- return controlsAttribute !== void 0 && !isStaticallyFalseBooleanAttribute(controlsAttribute);
21496
- }
21412
+ case "video": return hasJsxPropIgnoreCase(openingElement.attributes, "controls") !== void 0;
21497
21413
  default: return false;
21498
21414
  }
21499
21415
  };
@@ -22457,8 +22373,7 @@ const SECOND_INDEX_METHODS = new Set([
22457
22373
  "some"
22458
22374
  ]);
22459
22375
  const THIRD_INDEX_METHODS = new Set(["reduce", "reduceRight"]);
22460
- const isPositionallyStableIterationReceiver = (receiverNode) => {
22461
- const receiver = stripParenExpression(receiverNode);
22376
+ const isPositionallyStableIterationReceiver = (receiver) => {
22462
22377
  if (isAllLiteralArrayExpression(receiver)) return true;
22463
22378
  if (isNodeOfType(receiver, "ArrayExpression") && receiver.elements?.length === 1) {
22464
22379
  const only = receiver.elements[0];
@@ -22469,13 +22384,17 @@ const isPositionallyStableIterationReceiver = (receiverNode) => {
22469
22384
  }
22470
22385
  if (!isNodeOfType(receiver, "CallExpression")) return false;
22471
22386
  const callee = receiver.callee;
22472
- if (isGlobalMethodCall(receiver, "Array", "from") && receiver.arguments.length >= 1 && isNodeOfType(receiver.arguments[0], "ObjectExpression")) return true;
22387
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "from" && receiver.arguments.length >= 1 && isNodeOfType(receiver.arguments[0], "ObjectExpression")) return true;
22473
22388
  if (isNodeOfType(callee, "Identifier") && callee.name === "Array") return true;
22474
22389
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "split") return true;
22475
22390
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && (callee.property.name === "fill" || callee.property.name === "flat")) return isPositionallyStableIterationReceiver(callee.object);
22476
22391
  return false;
22477
22392
  };
22478
- const isArrayFromMapperCallback = (parentCall, callback) => parentCall.arguments[1] === callback && isGlobalMethodCall(parentCall, "Array", "from");
22393
+ const isArrayFromMapperCallback = (parentCall, callback) => {
22394
+ if (parentCall.arguments[1] !== callback) return false;
22395
+ const callee = parentCall.callee;
22396
+ return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "from";
22397
+ };
22479
22398
  const isArrayFromSourcePositionallyStable = (source) => {
22480
22399
  if (isNodeOfType(source, "ObjectExpression")) {
22481
22400
  for (const property of source.properties ?? []) {
@@ -22534,12 +22453,18 @@ const expressionUsesIndex = (expression, paramName) => {
22534
22453
  return false;
22535
22454
  }
22536
22455
  if (isNodeOfType(expression, "CallExpression")) {
22537
- if (isNodeOfType(expression.callee, "MemberExpression") && isNodeOfType(expression.callee.property, "Identifier") && expression.callee.property.name === "toString" && isIndexReference(stripParenExpression(expression.callee.object), paramName)) return true;
22456
+ if (isNodeOfType(expression.callee, "MemberExpression") && isNodeOfType(expression.callee.property, "Identifier") && expression.callee.property.name === "toString" && isIndexReference(expression.callee.object, paramName)) return true;
22538
22457
  if (isNodeOfType(expression.callee, "Identifier") && expression.callee.name === "String" && expression.arguments.length > 0 && isIndexReference(expression.arguments[0], paramName)) return true;
22539
22458
  }
22540
22459
  return false;
22541
22460
  };
22542
- const isReactCloneElement = (callExpression) => isGlobalMethodCall(callExpression, "React", "cloneElement");
22461
+ const isReactCloneElement = (callExpression) => {
22462
+ const callee = callExpression.callee;
22463
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
22464
+ if (!isNodeOfType(callee.property, "Identifier")) return false;
22465
+ if (callee.property.name !== "cloneElement") return false;
22466
+ return isNodeOfType(callee.object, "Identifier") && callee.object.name === "React";
22467
+ };
22543
22468
  const noArrayIndexKey = defineRule({
22544
22469
  id: "no-array-index-key",
22545
22470
  title: "Array index used as a key",
@@ -23263,7 +23188,7 @@ const isAsyncFunctionLike = (node) => {
23263
23188
  if (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) return Boolean(node.async);
23264
23189
  return false;
23265
23190
  };
23266
- const SYNCHRONOUS_ITERATION_METHOD_NAMES$1 = new Set([
23191
+ const SYNCHRONOUS_ITERATION_METHOD_NAMES = new Set([
23267
23192
  "forEach",
23268
23193
  "map",
23269
23194
  "filter",
@@ -23284,51 +23209,30 @@ const runsOnEffectDispatch = (functionNode) => {
23284
23209
  if (parent.callee === functionNode) return true;
23285
23210
  if (!(parent.arguments ?? []).some((argument) => argument === functionNode)) return false;
23286
23211
  const callee = parent.callee;
23287
- return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && SYNCHRONOUS_ITERATION_METHOD_NAMES$1.has(callee.property.name);
23212
+ return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && SYNCHRONOUS_ITERATION_METHOD_NAMES.has(callee.property.name);
23288
23213
  };
23289
23214
  const isTerminatingStatement = (statement) => isNodeOfType(statement, "BreakStatement") || isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement") || isNodeOfType(statement, "ContinueStatement");
23290
- const analyzeBranchStatements = (branch, context) => analyzeStatementSequence(isNodeOfType(branch, "BlockStatement") ? branch.body ?? [] : [branch], context);
23291
- const analyzeStatementSequence = (statements, context) => {
23215
+ const isGuardWithTerminatingBranch = (statement) => {
23216
+ if (!isNodeOfType(statement, "IfStatement")) return null;
23217
+ if (statement.alternate) return null;
23218
+ const consequent = statement.consequent;
23219
+ if (isTerminatingStatement(consequent)) return consequent;
23220
+ if (isNodeOfType(consequent, "BlockStatement") && (consequent.body ?? []).some((inner) => isTerminatingStatement(inner))) return consequent;
23221
+ return null;
23222
+ };
23223
+ const countStatementSequenceSetStateCalls = (statements, context) => {
23292
23224
  let fallThroughCount = 0;
23293
- let maxTerminatedCount = 0;
23225
+ let maxTerminatingPathCount = 0;
23294
23226
  for (const statement of statements) {
23295
- if (isNodeOfType(statement, "IfStatement")) {
23296
- fallThroughCount += countMaxPathSetStateCalls(statement.test, context);
23297
- const thenSummary = analyzeBranchStatements(statement.consequent, context);
23298
- const elseSummary = statement.alternate ? analyzeBranchStatements(statement.alternate, context) : {
23299
- fallThroughCount: 0,
23300
- maxTerminatedCount: 0,
23301
- doAllPathsTerminate: false
23302
- };
23303
- maxTerminatedCount = Math.max(maxTerminatedCount, fallThroughCount + thenSummary.maxTerminatedCount, fallThroughCount + elseSummary.maxTerminatedCount);
23304
- if (thenSummary.doAllPathsTerminate && elseSummary.doAllPathsTerminate) return {
23305
- fallThroughCount: 0,
23306
- maxTerminatedCount,
23307
- doAllPathsTerminate: true
23308
- };
23309
- const fallThroughBranchCounts = [...thenSummary.doAllPathsTerminate ? [] : [thenSummary.fallThroughCount], ...elseSummary.doAllPathsTerminate ? [] : [elseSummary.fallThroughCount]];
23310
- fallThroughCount += Math.max(...fallThroughBranchCounts);
23227
+ const guardBranch = isGuardWithTerminatingBranch(statement);
23228
+ if (guardBranch) {
23229
+ maxTerminatingPathCount = Math.max(maxTerminatingPathCount, fallThroughCount + countMaxPathSetStateCalls(guardBranch, context));
23311
23230
  continue;
23312
23231
  }
23313
- if (isTerminatingStatement(statement)) {
23314
- const terminatedPathCount = fallThroughCount + countMaxPathSetStateCalls(statement, context);
23315
- return {
23316
- fallThroughCount: 0,
23317
- maxTerminatedCount: Math.max(maxTerminatedCount, terminatedPathCount),
23318
- doAllPathsTerminate: true
23319
- };
23320
- }
23232
+ if (isTerminatingStatement(statement)) break;
23321
23233
  fallThroughCount += countMaxPathSetStateCalls(statement, context);
23322
23234
  }
23323
- return {
23324
- fallThroughCount,
23325
- maxTerminatedCount,
23326
- doAllPathsTerminate: false
23327
- };
23328
- };
23329
- const countStatementSequenceSetStateCalls = (statements, context) => {
23330
- const summary = analyzeStatementSequence(statements, context);
23331
- return Math.max(summary.fallThroughCount, summary.maxTerminatedCount);
23235
+ return Math.max(maxTerminatingPathCount, fallThroughCount);
23332
23236
  };
23333
23237
  const collectLocalHelperFunctions = (root) => {
23334
23238
  const helpersByName = /* @__PURE__ */ new Map();
@@ -23459,9 +23363,7 @@ const isDevOnlyGuardedEffect = (callback) => {
23459
23363
  if (!body || !isNodeOfType(body, "BlockStatement")) return false;
23460
23364
  const firstStatement = (body.body ?? [])[0];
23461
23365
  if (!firstStatement) return false;
23462
- if (!isNodeOfType(firstStatement, "IfStatement") || firstStatement.alternate) return false;
23463
- const consequent = firstStatement.consequent;
23464
- if (!(isTerminatingStatement(consequent) || isNodeOfType(consequent, "BlockStatement") && (consequent.body ?? []).some((inner) => isTerminatingStatement(inner)))) return false;
23366
+ if (!isGuardWithTerminatingBranch(firstStatement)) return false;
23465
23367
  return mentionsDevEnvFlag(firstStatement.test);
23466
23368
  };
23467
23369
  const noCascadingSetState = defineRule({
@@ -24790,21 +24692,11 @@ const isInitialOnlySetterCall = (callExpr) => {
24790
24692
  return isInitialOnlyPropName(arg.name);
24791
24693
  };
24792
24694
  //#endregion
24793
- //#region src/plugin/utils/is-no-op-statement.ts
24794
- const isNoOpStatement = (statement) => {
24795
- if (isNodeOfType(statement, "EmptyStatement")) return true;
24796
- if (!isNodeOfType(statement, "ExpressionStatement")) return false;
24797
- const expression = stripParenExpression(statement.expression);
24798
- if (isNodeOfType(expression, "Literal")) return true;
24799
- if (isNodeOfType(expression, "Identifier")) return expression.name === "undefined";
24800
- if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return isNodeOfType(stripParenExpression(expression.argument), "Literal");
24801
- return false;
24802
- };
24803
- //#endregion
24804
24695
  //#region src/plugin/utils/get-callback-statements.ts
24805
24696
  const getCallbackStatements = (callback) => {
24806
24697
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") && !isNodeOfType(callback, "FunctionDeclaration")) return [];
24807
- return (isNodeOfType(callback.body, "BlockStatement") ? callback.body.body ?? [] : callback.body ? [callback.body] : []).filter((statement) => !isNoOpStatement(statement));
24698
+ if (isNodeOfType(callback.body, "BlockStatement")) return callback.body.body ?? [];
24699
+ return callback.body ? [callback.body] : [];
24808
24700
  };
24809
24701
  //#endregion
24810
24702
  //#region src/plugin/rules/state-and-effects/no-derived-state-effect.ts
@@ -24846,7 +24738,6 @@ const collectValueIdentifierNames = (node, into, localBindingNames = /* @__PURE_
24846
24738
  const flattenGuardedStatements = (statements) => {
24847
24739
  const flattened = [];
24848
24740
  for (const statement of statements) {
24849
- if (isNoOpStatement(statement)) continue;
24850
24741
  if (isNodeOfType(statement, "ExpressionStatement")) {
24851
24742
  flattened.push(statement);
24852
24743
  continue;
@@ -25531,7 +25422,7 @@ const noDidMountSetState = defineRule({
25531
25422
  const { mode } = resolveSettings$20(context.settings);
25532
25423
  return { CallExpression(node) {
25533
25424
  if (!isNodeOfType(node.callee, "MemberExpression")) return;
25534
- if (!isNodeOfType(stripParenExpression(node.callee.object), "ThisExpression")) return;
25425
+ if (!isNodeOfType(node.callee.object, "ThisExpression")) return;
25535
25426
  if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
25536
25427
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$2, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
25537
25428
  if (isMountFlagArgument(node.arguments?.[0])) return;
@@ -25656,7 +25547,7 @@ const noDidUpdateSetState = defineRule({
25656
25547
  const { mode } = resolveSettings$19(context.settings);
25657
25548
  return { CallExpression(node) {
25658
25549
  if (!isNodeOfType(node.callee, "MemberExpression")) return;
25659
- if (!isNodeOfType(stripParenExpression(node.callee.object), "ThisExpression")) return;
25550
+ if (!isNodeOfType(node.callee.object, "ThisExpression")) return;
25660
25551
  if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
25661
25552
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
25662
25553
  if (isInsideDiffGuard(node)) return;
@@ -25779,33 +25670,6 @@ const initializerMarksPlainState = (initializerArgument) => {
25779
25670
  }
25780
25671
  return producesPlainStateValue(unwrapped);
25781
25672
  };
25782
- const producesOpaqueInstanceValue = (expression) => {
25783
- if (isNodeOfType(expression, "NewExpression")) return true;
25784
- return isNodeOfType(expression, "CallExpression") && isNodeOfType(expression.callee, "MemberExpression") && !isPlainDataProducerCall(expression);
25785
- };
25786
- const collectSetterValueObservations = (componentBody, setterNames) => {
25787
- const plainFedSetterNames = /* @__PURE__ */ new Set();
25788
- const opaqueFedSetterNames = /* @__PURE__ */ new Set();
25789
- walkAst(componentBody, (node) => {
25790
- if (!isNodeOfType(node, "CallExpression")) return;
25791
- if (!isNodeOfType(node.callee, "Identifier")) return;
25792
- const setterName = node.callee.name;
25793
- if (!setterNames.has(setterName)) return;
25794
- const argument = node.arguments?.[0];
25795
- if (!argument) return;
25796
- const unwrapped = stripParenExpression(argument);
25797
- if (isNullOrUndefinedExpression(unwrapped)) return;
25798
- if (producesPlainStateValue(unwrapped)) {
25799
- plainFedSetterNames.add(setterName);
25800
- return;
25801
- }
25802
- if (producesOpaqueInstanceValue(unwrapped)) opaqueFedSetterNames.add(setterName);
25803
- });
25804
- return {
25805
- plainFedSetterNames,
25806
- opaqueFedSetterNames
25807
- };
25808
- };
25809
25673
  const collectCallbackRefSetterNames = (componentBody) => {
25810
25674
  const callbackRefSetterNames = /* @__PURE__ */ new Set();
25811
25675
  walkAst(componentBody, (node) => {
@@ -25875,15 +25739,11 @@ const noDirectStateMutation = defineRule({
25875
25739
  if (bindings.length === 0) return;
25876
25740
  const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
25877
25741
  const callbackRefSetterNames = collectCallbackRefSetterNames(componentBody);
25878
- const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
25879
25742
  const plainObjectStateValueNames = /* @__PURE__ */ new Set();
25880
25743
  for (const binding of bindings) {
25881
25744
  if (callbackRefSetterNames.has(binding.setterName)) continue;
25882
25745
  if (!isNodeOfType(binding.declarator.init, "CallExpression")) continue;
25883
- const initializerArgument = binding.declarator.init.arguments?.[0];
25884
- if (!initializerMarksPlainState(initializerArgument)) continue;
25885
- if ((!initializerArgument || isNullOrUndefinedExpression(stripParenExpression(initializerArgument))) && setterValueObservations.opaqueFedSetterNames.has(binding.setterName) && !setterValueObservations.plainFedSetterNames.has(binding.setterName)) continue;
25886
- plainObjectStateValueNames.add(binding.valueName);
25746
+ if (initializerMarksPlainState(binding.declarator.init.arguments?.[0])) plainObjectStateValueNames.add(binding.valueName);
25887
25747
  }
25888
25748
  const visitMutationCandidate = (child, currentlyShadowed) => {
25889
25749
  if (isNodeOfType(child, "AssignmentExpression")) {
@@ -26008,10 +25868,9 @@ const noDocumentStartViewTransition = defineRule({
26008
25868
  create: (context) => ({ CallExpression(node) {
26009
25869
  const callee = node.callee;
26010
25870
  if (!isNodeOfType(callee, "MemberExpression")) return;
26011
- const receiver = stripParenExpression(callee.object);
26012
- if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "document") return;
25871
+ if (!isNodeOfType(callee.object, "Identifier") || callee.object.name !== "document") return;
26013
25872
  if (!isNodeOfType(callee.property, "Identifier") || callee.property.name !== "startViewTransition") return;
26014
- if (context.scopes.symbolFor(receiver) !== null) return;
25873
+ if (context.scopes.symbolFor(callee.object) !== null) return;
26015
25874
  if (!importsReactViewTransition(node)) return;
26016
25875
  context.report({
26017
25876
  node,
@@ -26031,8 +25890,7 @@ const noDocumentWrite = defineRule({
26031
25890
  create: (context) => ({ CallExpression(node) {
26032
25891
  const callee = node.callee;
26033
25892
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
26034
- const receiver = stripParenExpression(callee.object);
26035
- if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "document") return;
25893
+ if (!isNodeOfType(callee.object, "Identifier") || callee.object.name !== "document") return;
26036
25894
  if (!isNodeOfType(callee.property, "Identifier") || !WRITE_METHODS.has(callee.property.name)) return;
26037
25895
  context.report({
26038
25896
  node,
@@ -26664,6 +26522,13 @@ const noEffectEventHandler = defineRule({
26664
26522
  }
26665
26523
  });
26666
26524
  //#endregion
26525
+ //#region src/plugin/utils/is-imported-from-non-react-module.ts
26526
+ const isImportedFromNonReactModule = (contextNode, localIdentifierName) => {
26527
+ const importSource = getImportSourceForName(contextNode, localIdentifierName);
26528
+ if (importSource === null) return false;
26529
+ return !REACT_RUNTIME_MODULE_SOURCES.has(importSource);
26530
+ };
26531
+ //#endregion
26667
26532
  //#region src/plugin/rules/state-and-effects/no-effect-event-in-deps.ts
26668
26533
  const createComponentBindingStackTracker = (callbacks) => {
26669
26534
  const componentBindingStack = [];
@@ -26717,7 +26582,11 @@ const noEffectEventInDeps = defineRule({
26717
26582
  const initializer = declaratorNode.init;
26718
26583
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return;
26719
26584
  if (!isHookCall$2(initializer, "useEffectEvent")) return;
26720
- if (isNonReactEffectEventCallee(initializer.callee, declaratorNode, context.scopes)) return;
26585
+ if (isNodeOfType(initializer.callee, "Identifier")) {
26586
+ if (isImportedFromNonReactModule(declaratorNode, initializer.callee.name)) return;
26587
+ const calleeSymbol = context.scopes.referenceFor(initializer.callee)?.resolvedSymbol;
26588
+ if (calleeSymbol && calleeSymbol.kind !== "import") return;
26589
+ }
26721
26590
  componentBindings.addBindingToCurrentFrame(declaratorNode.id.name);
26722
26591
  } });
26723
26592
  return {
@@ -29297,7 +29166,7 @@ const noIsMounted = defineRule({
29297
29166
  recommendation: "`isMounted` doesn't work in modern React. Track mount state with a ref, or cancel the async work instead.",
29298
29167
  create: (context) => ({ CallExpression(node) {
29299
29168
  if (!isNodeOfType(node.callee, "MemberExpression")) return;
29300
- if (!isNodeOfType(stripParenExpression(node.callee.object), "ThisExpression")) return;
29169
+ if (!isNodeOfType(node.callee.object, "ThisExpression")) return;
29301
29170
  if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "isMounted") return;
29302
29171
  if (!getParentComponent(node)) return;
29303
29172
  context.report({
@@ -29312,9 +29181,7 @@ const MESSAGE$20 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializin
29312
29181
  const isJsonMethodCall = (node, method) => {
29313
29182
  if (!isNodeOfType(node, "CallExpression")) return false;
29314
29183
  const callee = node.callee;
29315
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return false;
29316
- const receiver = stripParenExpression(callee.object);
29317
- return isNodeOfType(receiver, "Identifier") && receiver.name === "JSON" && isNodeOfType(callee.property, "Identifier") && callee.property.name === method;
29184
+ return isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.object, "Identifier") && callee.object.name === "JSON" && isNodeOfType(callee.property, "Identifier") && callee.property.name === method;
29318
29185
  };
29319
29186
  const SNAPSHOT_FUNCTION_NAME_PATTERN = /snapshot|serializ|tojson/i;
29320
29187
  const NORMALIZATION_BINDING_NAME_PATTERN = /normali[sz]/i;
@@ -29725,378 +29592,6 @@ const noLegacyContextApi = defineRule({
29725
29592
  }
29726
29593
  });
29727
29594
  //#endregion
29728
- //#region src/plugin/utils/executes-during-render.ts
29729
- const SYNCHRONOUS_ITERATION_METHOD_NAMES = new Set([
29730
- "map",
29731
- "filter",
29732
- "forEach",
29733
- "flatMap",
29734
- "reduce",
29735
- "reduceRight",
29736
- "some",
29737
- "every",
29738
- "find",
29739
- "findIndex",
29740
- "findLast",
29741
- "findLastIndex",
29742
- "sort",
29743
- "toSorted"
29744
- ]);
29745
- const executesDuringRender = (functionNode) => {
29746
- const parent = functionNode.parent;
29747
- if (!isNodeOfType(parent, "CallExpression")) return false;
29748
- if (parent.callee === functionNode) return true;
29749
- if (isHookCall$2(parent, "useMemo") && parent.arguments?.[0] === functionNode) return true;
29750
- return isNodeOfType(parent.callee, "MemberExpression") && !parent.callee.computed && isNodeOfType(parent.callee.property, "Identifier") && SYNCHRONOUS_ITERATION_METHOD_NAMES.has(parent.callee.property.name) && parent.arguments?.[0] === functionNode;
29751
- };
29752
- //#endregion
29753
- //#region src/plugin/utils/has-suppress-hydration-warning-attribute.ts
29754
- const hasSuppressHydrationWarningAttribute = (openingElement) => {
29755
- if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement")) return false;
29756
- for (const attr of openingElement.attributes ?? []) if (isNodeOfType(attr, "JSXAttribute") && isNodeOfType(attr.name, "JSXIdentifier") && attr.name.name === "suppressHydrationWarning") return true;
29757
- return false;
29758
- };
29759
- //#endregion
29760
- //#region src/plugin/utils/find-declarator-for-binding.ts
29761
- const findDeclaratorForBinding = (bindingIdentifier) => {
29762
- let ancestor = bindingIdentifier.parent;
29763
- while (ancestor) {
29764
- if (isNodeOfType(ancestor, "VariableDeclarator")) return ancestor;
29765
- if (isNodeOfType(ancestor, "FunctionDeclaration") || isNodeOfType(ancestor, "FunctionExpression") || isNodeOfType(ancestor, "ArrowFunctionExpression") || isNodeOfType(ancestor, "Program")) return null;
29766
- ancestor = ancestor.parent ?? null;
29767
- }
29768
- return null;
29769
- };
29770
- //#endregion
29771
- //#region src/plugin/utils/references-falsy-initial-state.ts
29772
- const isFalsyLiteral = (node) => {
29773
- if (!node) return true;
29774
- if (isNodeOfType(node, "Literal")) return !node.value;
29775
- return isNodeOfType(node, "Identifier") && node.name === "undefined";
29776
- };
29777
- const isFalsyInitialStateBinding = (identifier) => {
29778
- if (!isNodeOfType(identifier, "Identifier")) return false;
29779
- const binding = findVariableInitializer(identifier, identifier.name);
29780
- if (!binding) return false;
29781
- const declarator = findDeclaratorForBinding(binding.bindingIdentifier);
29782
- if (!declarator?.init) return false;
29783
- const init = stripParenExpression(declarator.init);
29784
- if (!isHookCall$2(init, "useState") || !isNodeOfType(init, "CallExpression")) return false;
29785
- if (!isNodeOfType(declarator.id, "ArrayPattern")) return false;
29786
- if (declarator.id.elements?.[0] !== binding.bindingIdentifier) return false;
29787
- return isFalsyLiteral(init.arguments?.[0]);
29788
- };
29789
- const referencesFalsyInitialState = (expression) => flattenLogicalAndChain(stripParenExpression(expression)).some((operand) => isFalsyInitialStateBinding(stripParenExpression(operand)));
29790
- //#endregion
29791
- //#region src/plugin/utils/is-gated-by-falsy-initial-state.ts
29792
- const isGatedByFalsyInitialState = (node) => {
29793
- let cursor = node;
29794
- let parent = node.parent;
29795
- while (parent) {
29796
- if (isNodeOfType(parent, "LogicalExpression") && parent.operator === "&&" && parent.right === cursor && referencesFalsyInitialState(parent.left)) return true;
29797
- if (isNodeOfType(parent, "ConditionalExpression") && parent.consequent === cursor && referencesFalsyInitialState(parent.test)) return true;
29798
- if (isNodeOfType(parent, "IfStatement") && parent.consequent === cursor && referencesFalsyInitialState(parent.test)) return true;
29799
- cursor = parent;
29800
- parent = parent.parent ?? null;
29801
- }
29802
- return false;
29803
- };
29804
- //#endregion
29805
- //#region src/plugin/utils/references-client-only-flag.ts
29806
- const CLIENT_ONLY_FLAG_NAME_PATTERN = /^(?:is|has|did)?_?(?:client|mounted|hydrated|browser)(?:_?(?:side|ready|only))?$/i;
29807
- const referencesClientOnlyFlag = (expression) => {
29808
- const unwrapped = stripParenExpression(expression);
29809
- if (isNodeOfType(unwrapped, "Identifier")) return CLIENT_ONLY_FLAG_NAME_PATTERN.test(unwrapped.name);
29810
- if (isNodeOfType(unwrapped, "MemberExpression")) {
29811
- const property = unwrapped.property;
29812
- return isNodeOfType(property, "Identifier") && CLIENT_ONLY_FLAG_NAME_PATTERN.test(property.name);
29813
- }
29814
- if (isNodeOfType(unwrapped, "UnaryExpression") && unwrapped.operator === "!") return referencesClientOnlyFlag(unwrapped.argument);
29815
- if (isNodeOfType(unwrapped, "LogicalExpression")) return referencesClientOnlyFlag(unwrapped.left) || referencesClientOnlyFlag(unwrapped.right);
29816
- return false;
29817
- };
29818
- //#endregion
29819
- //#region src/plugin/utils/is-inside-client-only-guard.ts
29820
- const isInsideClientOnlyGuard = (node) => {
29821
- let cursor = node;
29822
- let parent = node.parent;
29823
- while (parent) {
29824
- if (isNodeOfType(parent, "LogicalExpression") && parent.operator === "&&" && parent.right === cursor && referencesClientOnlyFlag(parent.left)) return true;
29825
- if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === cursor || parent.alternate === cursor) && referencesClientOnlyFlag(parent.test)) return true;
29826
- if (isNodeOfType(parent, "IfStatement") && parent.consequent === cursor && referencesClientOnlyFlag(parent.test)) return true;
29827
- cursor = parent;
29828
- parent = parent.parent ?? null;
29829
- }
29830
- return false;
29831
- };
29832
- //#endregion
29833
- //#region src/plugin/rules/performance/no-locale-format-in-render.ts
29834
- const LOCALE_FORMAT_METHOD_NAMES = new Set([
29835
- "toLocaleString",
29836
- "toLocaleDateString",
29837
- "toLocaleTimeString"
29838
- ]);
29839
- const DATE_ONLY_LOCALE_METHOD_NAMES = new Set(["toLocaleDateString", "toLocaleTimeString"]);
29840
- const INTL_FORMATTER_NAMES = new Set(["DateTimeFormat", "RelativeTimeFormat"]);
29841
- const INTL_FORMAT_METHOD_NAMES = new Set([
29842
- "format",
29843
- "formatToParts",
29844
- "formatRange"
29845
- ]);
29846
- const isProvableDateExpression = (expression) => {
29847
- if (!expression) return false;
29848
- const unwrapped = stripParenExpression(expression);
29849
- return isNodeOfType(unwrapped, "NewExpression") && isNodeOfType(unwrapped.callee, "Identifier") && unwrapped.callee.name === "Date";
29850
- };
29851
- const DATE_FLAVORED_NAME_PATTERN = /(date|time|timestamp|deadline|created|updated|scheduled|expire|moment|when|birthday|dob)|(at)$/i;
29852
- const receiverNameLooksDateFlavored = (expression) => {
29853
- if (!expression) return false;
29854
- const unwrapped = stripParenExpression(expression);
29855
- if (isNodeOfType(unwrapped, "Identifier")) return DATE_FLAVORED_NAME_PATTERN.test(unwrapped.name);
29856
- if (isNodeOfType(unwrapped, "MemberExpression") && !unwrapped.computed) return isNodeOfType(unwrapped.property, "Identifier") && DATE_FLAVORED_NAME_PATTERN.test(unwrapped.property.name);
29857
- if (isNodeOfType(unwrapped, "CallExpression")) return receiverNameLooksDateFlavored(unwrapped.callee);
29858
- return false;
29859
- };
29860
- const objectLiteralHasProperty = (objectExpression, propertyName) => {
29861
- if (!objectExpression) return false;
29862
- const unwrapped = stripParenExpression(objectExpression);
29863
- if (!isNodeOfType(unwrapped, "ObjectExpression")) return false;
29864
- for (const property of unwrapped.properties ?? []) {
29865
- if (!isNodeOfType(property, "Property")) continue;
29866
- if (property.computed) continue;
29867
- if (isNodeOfType(property.key, "Identifier") && property.key.name === propertyName) return true;
29868
- if (isNodeOfType(property.key, "Literal") && property.key.value === propertyName) return true;
29869
- }
29870
- return false;
29871
- };
29872
- const hasExplicitLocaleArgument = (argument) => {
29873
- if (!argument) return false;
29874
- const unwrapped = stripParenExpression(argument);
29875
- if (isNodeOfType(unwrapped, "Identifier") && unwrapped.name === "undefined") return false;
29876
- return true;
29877
- };
29878
- const isDeterministicLocaleMethodCall = (call, methodName, receiverIsProvablyDate) => {
29879
- const localeArgument = call.arguments?.[0];
29880
- if (!hasExplicitLocaleArgument(localeArgument)) return false;
29881
- const optionsArgument = call.arguments?.[1];
29882
- if (objectLiteralHasProperty(optionsArgument, "timeZone")) return true;
29883
- return !DATE_ONLY_LOCALE_METHOD_NAMES.has(methodName) && !receiverIsProvablyDate;
29884
- };
29885
- const matchLocaleMethodCall = (call) => {
29886
- const callee = call.callee;
29887
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
29888
- if (!isNodeOfType(callee.property, "Identifier")) return null;
29889
- const methodName = callee.property.name;
29890
- if (!LOCALE_FORMAT_METHOD_NAMES.has(methodName)) return null;
29891
- const receiverIsProvablyDate = isProvableDateExpression(callee.object);
29892
- if (methodName === "toLocaleString" && !receiverIsProvablyDate && !receiverNameLooksDateFlavored(callee.object)) return null;
29893
- if (isDeterministicLocaleMethodCall(call, methodName, receiverIsProvablyDate)) return null;
29894
- return {
29895
- node: call,
29896
- display: `${methodName}()`
29897
- };
29898
- };
29899
- const getIntlFormatterName = (expression) => {
29900
- if (!expression) return null;
29901
- const unwrapped = stripParenExpression(expression);
29902
- if (!isNodeOfType(unwrapped, "CallExpression") && !isNodeOfType(unwrapped, "NewExpression")) return null;
29903
- const callee = unwrapped.callee;
29904
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
29905
- if (!isNodeOfType(callee.object, "Identifier") || callee.object.name !== "Intl") return null;
29906
- if (!isNodeOfType(callee.property, "Identifier")) return null;
29907
- return INTL_FORMATTER_NAMES.has(callee.property.name) ? callee.property.name : null;
29908
- };
29909
- const isDeterministicIntlConstruction = (construction, formatterName) => {
29910
- if (!isNodeOfType(construction, "CallExpression") && !isNodeOfType(construction, "NewExpression")) return false;
29911
- if (!hasExplicitLocaleArgument(construction.arguments?.[0])) return false;
29912
- if (formatterName !== "DateTimeFormat") return true;
29913
- return objectLiteralHasProperty(construction.arguments?.[1], "timeZone");
29914
- };
29915
- const matchIntlFormatCall = (call) => {
29916
- const callee = call.callee;
29917
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
29918
- if (!isNodeOfType(callee.property, "Identifier")) return null;
29919
- if (!INTL_FORMAT_METHOD_NAMES.has(callee.property.name)) return null;
29920
- let construction = stripParenExpression(callee.object);
29921
- if (isNodeOfType(construction, "Identifier")) {
29922
- const binding = findVariableInitializer(construction, construction.name);
29923
- construction = binding?.initializer ? stripParenExpression(binding.initializer) : null;
29924
- }
29925
- if (!construction) return null;
29926
- const formatterName = getIntlFormatterName(construction);
29927
- if (!formatterName) return null;
29928
- if (isDeterministicIntlConstruction(construction, formatterName)) return null;
29929
- return {
29930
- node: call,
29931
- display: `Intl.${formatterName}().${callee.property.name}()`
29932
- };
29933
- };
29934
- const isDeterministicInputDateConstruction = (expression) => {
29935
- if (!expression) return false;
29936
- const unwrapped = stripParenExpression(expression);
29937
- if (!isNodeOfType(unwrapped, "NewExpression")) return false;
29938
- if (!isNodeOfType(unwrapped.callee, "Identifier") || unwrapped.callee.name !== "Date") return false;
29939
- return (unwrapped.arguments?.length ?? 0) > 0;
29940
- };
29941
- const matchDateDefaultStringification = (node) => {
29942
- if (isNodeOfType(node, "CallExpression")) {
29943
- const callee = node.callee;
29944
- if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "toString" && isDeterministicInputDateConstruction(callee.object)) return {
29945
- node,
29946
- display: "Date.prototype.toString()"
29947
- };
29948
- if (isNodeOfType(callee, "Identifier") && callee.name === "String" && isDeterministicInputDateConstruction(node.arguments?.[0])) return {
29949
- node,
29950
- display: "String(new Date(…))"
29951
- };
29952
- return null;
29953
- }
29954
- if (isNodeOfType(node, "TemplateLiteral")) {
29955
- for (const expression of node.expressions ?? []) if (isDeterministicInputDateConstruction(expression)) return {
29956
- node: expression,
29957
- display: "`${new Date(…)}`"
29958
- };
29959
- }
29960
- return null;
29961
- };
29962
- const findRenderPhaseComponentOrHook = (node) => {
29963
- let functionNode = findEnclosingFunction(node);
29964
- while (functionNode) {
29965
- if (componentOrHookDisplayNameForFunction(functionNode)) return functionNode;
29966
- if (!executesDuringRender(functionNode)) return null;
29967
- functionNode = findEnclosingFunction(functionNode);
29968
- }
29969
- return null;
29970
- };
29971
- const hasClientRenderEvidence = (componentOrHookNode, fileHasUseClientDirective) => {
29972
- if (fileHasUseClientDirective) return true;
29973
- const displayName = componentOrHookDisplayNameForFunction(componentOrHookNode);
29974
- if (displayName && isReactHookName(displayName)) return true;
29975
- let callsHook = false;
29976
- walkAst((isFunctionLike$1(componentOrHookNode) ? componentOrHookNode.body : null) ?? componentOrHookNode, (child) => {
29977
- if (callsHook) return false;
29978
- if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && isReactHookName(child.callee.name)) {
29979
- callsHook = true;
29980
- return false;
29981
- }
29982
- });
29983
- return callsHook;
29984
- };
29985
- const isAfterClientOnlyEarlyReturn = (node, componentOrHookNode) => {
29986
- const body = isFunctionLike$1(componentOrHookNode) ? componentOrHookNode.body : null;
29987
- if (!isNodeOfType(body, "BlockStatement")) return false;
29988
- const ancestors = /* @__PURE__ */ new Set();
29989
- let cursor = node;
29990
- while (cursor) {
29991
- ancestors.add(cursor);
29992
- cursor = cursor.parent ?? null;
29993
- }
29994
- for (const statement of body.body ?? []) {
29995
- if (ancestors.has(statement)) return false;
29996
- if (!isNodeOfType(statement, "IfStatement")) continue;
29997
- if (!referencesClientOnlyFlag(statement.test) && !referencesFalsyInitialState(statement.test)) continue;
29998
- let returnsEarly = false;
29999
- walkAst(statement.consequent, (child) => {
30000
- if (isFunctionLike$1(child)) return false;
30001
- if (isNodeOfType(child, "ReturnStatement")) {
30002
- returnsEarly = true;
30003
- return false;
30004
- }
30005
- });
30006
- if (returnsEarly) return true;
30007
- }
30008
- return false;
30009
- };
30010
- const findEnclosingJsxOpeningElement = (node) => {
30011
- let cursor = node.parent;
30012
- while (cursor) {
30013
- if (isNodeOfType(cursor, "JSXElement")) return cursor.openingElement;
30014
- if (isNodeOfType(cursor, "JSXFragment")) return null;
30015
- cursor = cursor.parent ?? null;
30016
- }
30017
- return null;
30018
- };
30019
- const noLocaleFormatInRender = defineRule({
30020
- id: "no-locale-format-in-render",
30021
- title: "Locale/timezone formatting during render",
30022
- severity: "warn",
30023
- category: "Correctness",
30024
- disabledWhen: [
30025
- "vite",
30026
- "cra",
30027
- "expo",
30028
- "react-native",
30029
- "unknown"
30030
- ],
30031
- recommendation: "Format locale/timezone-dependent values in a post-mount useEffect + state, or pass an explicit locale and timeZone so the server and the browser render the same text. Only runs on SSR-capable projects.",
30032
- create: (context) => {
30033
- if (isTestlikeFilename(context.filename)) return {};
30034
- if (classifyReactNativeFileTarget(context) === "react-native") return {};
30035
- let fileHasUseClientDirective = false;
30036
- let fileIsEmailTemplate = false;
30037
- const reportedNodes = /* @__PURE__ */ new Set();
30038
- const reportIfRenderPhase = (match) => {
30039
- if (reportedNodes.has(match.node)) return;
30040
- const componentOrHookNode = findRenderPhaseComponentOrHook(match.node);
30041
- if (!componentOrHookNode) return;
30042
- if (fileIsEmailTemplate) return;
30043
- if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
30044
- if (isInsideClientOnlyGuard(match.node)) return;
30045
- if (isGatedByFalsyInitialState(match.node)) return;
30046
- if (isAfterClientOnlyEarlyReturn(match.node, componentOrHookNode)) return;
30047
- if (hasSuppressHydrationWarningAttribute(findEnclosingJsxOpeningElement(match.node))) return;
30048
- if (isGeneratedImageRenderContext(context, findEnclosingJsxOpeningElement(match.node)?.parent ?? match.node)) return;
30049
- reportedNodes.add(match.node);
30050
- context.report({
30051
- node: match.node,
30052
- message: `This can cause a hydration mismatch because ${match.display} formats with the server's locale and timezone during server rendering but the user's in the browser. Format it in a post-mount useEffect, or pass an explicit locale and timeZone.`
30053
- });
30054
- };
30055
- return {
30056
- Program(node) {
30057
- fileHasUseClientDirective = hasDirective(node, "use client");
30058
- fileIsEmailTemplate = hasEmailTemplateImport(node);
30059
- },
30060
- CallExpression(node) {
30061
- const match = matchLocaleMethodCall(node) ?? matchIntlFormatCall(node) ?? matchDateDefaultStringification(node);
30062
- if (match) reportIfRenderPhase(match);
30063
- },
30064
- TemplateLiteral(node) {
30065
- const match = matchDateDefaultStringification(node);
30066
- if (match) reportIfRenderPhase(match);
30067
- },
30068
- JSXExpressionContainer(node) {
30069
- const expression = stripParenExpression(node.expression);
30070
- if (!isNodeOfType(expression, "CallExpression")) return;
30071
- if (!isNodeOfType(expression.callee, "Identifier")) return;
30072
- const helperName = expression.callee.name;
30073
- const componentOrHookNode = findRenderPhaseComponentOrHook(node);
30074
- if (!componentOrHookNode) return;
30075
- const helperNode = findVariableInitializer(expression.callee, helperName)?.initializer;
30076
- if (!helperNode || !isFunctionLike$1(helperNode)) return;
30077
- if (componentOrHookDisplayNameForFunction(helperNode)) return;
30078
- walkAst(helperNode.body ?? helperNode, (child) => {
30079
- if (isFunctionLike$1(child)) return false;
30080
- if (!isNodeOfType(child, "CallExpression")) return;
30081
- const match = matchLocaleMethodCall(child) ?? matchIntlFormatCall(child);
30082
- if (!match || reportedNodes.has(match.node)) return;
30083
- if (fileIsEmailTemplate) return;
30084
- if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
30085
- if (isInsideClientOnlyGuard(node)) return;
30086
- if (isGatedByFalsyInitialState(node)) return;
30087
- if (isAfterClientOnlyEarlyReturn(node, componentOrHookNode)) return;
30088
- if (hasSuppressHydrationWarningAttribute(findEnclosingJsxOpeningElement(node))) return;
30089
- reportedNodes.add(match.node);
30090
- context.report({
30091
- node: match.node,
30092
- message: `This can cause a hydration mismatch because ${match.display} (reached from JSX through "${helperName}") formats with the server's locale and timezone during server rendering but the user's in the browser. Format it in a post-mount useEffect, or pass an explicit locale and timeZone.`
30093
- });
30094
- });
30095
- }
30096
- };
30097
- }
30098
- });
30099
- //#endregion
30100
29595
  //#region src/plugin/rules/design/no-long-transition-duration.ts
30101
29596
  const hasInfiniteIterationCount = (properties) => properties.some((property) => {
30102
29597
  if (getStylePropertyKey(property) !== "animationIterationCount") return false;
@@ -32209,12 +31704,12 @@ const noPassDataToParent = defineRule({
32209
31704
  if (calleeNode === identifier) {
32210
31705
  if (!isDirectParentCallbackRef(analysis, ref)) continue;
32211
31706
  if (isNodeOfType(identifier, "Identifier") && COMMAND_PROP_NAME_PATTERN.test(identifier.name)) continue;
32212
- } else if (isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === identifier) {
31707
+ } else if (isNodeOfType(calleeNode, "MemberExpression") && unwrapChainExpression(calleeNode.object) === identifier) {
32213
31708
  if (!isWholePropsObjectReference(analysis, ref)) continue;
32214
31709
  if (isCustomHookParameter(ref)) continue;
32215
31710
  } else continue;
32216
31711
  const methodName = getCallMethodName(calleeNode);
32217
- const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
31712
+ const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && isNodeOfType(calleeNode, "MemberExpression") && calleeNode.object === ref.identifier && isWholePropsObjectReference(analysis, ref));
32218
31713
  if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
32219
31714
  if (methodName && COMMAND_PROP_NAME_PATTERN.test(methodName)) continue;
32220
31715
  if (isNamespacedApiCallee(calleeNode)) continue;
@@ -32405,7 +31900,7 @@ const noPassLiveStateToParent = defineRule({
32405
31900
  if (isCallResultConsumedAsArgument(callExpr)) continue;
32406
31901
  const calleeNode = callExpr.callee;
32407
31902
  const methodName = calleeNode ? getCallMethodName(calleeNode) : null;
32408
- const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && calleeNode && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
31903
+ const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && calleeNode && isNodeOfType(calleeNode, "MemberExpression") && calleeNode.object === ref.identifier && isWholePropsObjectReference(analysis, ref));
32409
31904
  if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
32410
31905
  if (calleeNode && isNamespacedApiCallee(calleeNode)) continue;
32411
31906
  const stateArgRefs = collectPropCallbackBoundStateRefs(analysis, ref, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef));
@@ -32939,7 +32434,7 @@ const isAlwaysFreshExpression = (expression) => {
32939
32434
  return `${callee.name}()`;
32940
32435
  }
32941
32436
  if (isNodeOfType(callee, "MemberExpression") && !callee.computed) {
32942
- const receiver = stripParenExpression(callee.object);
32437
+ const receiver = callee.object;
32943
32438
  const property = callee.property;
32944
32439
  if (!isNodeOfType(property, "Identifier")) return null;
32945
32440
  if (isNodeOfType(receiver, "Identifier")) {
@@ -33060,10 +32555,8 @@ const createDeprecatedReactImportRule = ({ source, messages, handleExtraSource }
33060
32555
  if (typeof sourceValue !== "string") return;
33061
32556
  if (handleExtraSource?.(node, context)) return;
33062
32557
  if (sourceValue !== source) return;
33063
- if (isTypeOnlyImport(node)) return;
33064
32558
  for (const specifier of node.specifiers ?? []) {
33065
32559
  if (isNodeOfType(specifier, "ImportSpecifier")) {
33066
- if (specifier.importKind === "type") continue;
33067
32560
  const importedName = getImportedName$1(specifier);
33068
32561
  if (!importedName) continue;
33069
32562
  const message = messages.get(importedName);
@@ -33082,9 +32575,8 @@ const createDeprecatedReactImportRule = ({ source, messages, handleExtraSource }
33082
32575
  MemberExpression(node) {
33083
32576
  if (namespaceBindings.size === 0) return;
33084
32577
  if (node.computed) return;
33085
- const receiver = stripParenExpression(node.object);
33086
- if (!isNodeOfType(receiver, "Identifier")) return;
33087
- if (!namespaceBindings.has(receiver.name)) return;
32578
+ if (!isNodeOfType(node.object, "Identifier")) return;
32579
+ if (!namespaceBindings.has(node.object.name)) return;
33088
32580
  if (!isNodeOfType(node.property, "Identifier")) return;
33089
32581
  const message = messages.get(node.property.name);
33090
32582
  if (message) context.report({
@@ -33149,7 +32641,7 @@ const noReactDomDeprecatedApis = defineRule({
33149
32641
  });
33150
32642
  //#endregion
33151
32643
  //#region src/plugin/rules/architecture/no-react19-deprecated-apis.ts
33152
- const REACT_19_DEPRECATED_MESSAGES = new Map([["forwardRef", "forwardRef is dead weight in React 19, since ref is a normal prop now, so drop it & pass ref straight through."]]);
32644
+ const REACT_19_DEPRECATED_MESSAGES = new Map([["forwardRef", "forwardRef is dead weight in React 19, since ref is a normal prop now, so drop it & pass ref straight through."], ["useContext", "useContext is replaced by `use()` in React 19, which reads context inside ifs & loops too, so switch to `import { use } from 'react'`."]]);
33153
32645
  const isVendoredShadcnUiFilename = (rawFilename) => {
33154
32646
  if (!rawFilename) return false;
33155
32647
  const filename = rawFilename.replaceAll("\\", "/");
@@ -33187,7 +32679,7 @@ const noReact19DeprecatedApis = defineRule({
33187
32679
  requires: ["react:19"],
33188
32680
  tags: ["test-noise", "migration-hint"],
33189
32681
  severity: "warn",
33190
- recommendation: "Pass `ref` as a normal prop on function components, since `forwardRef` isn't needed in React 19. Only runs on React 19+ projects.",
32682
+ recommendation: "Pass `ref` as a normal prop on function components, since `forwardRef` isn't needed in React 19. Replace `useContext(X)` with `use(X)`. Only runs on React 19+ projects.",
33191
32683
  create: (context) => {
33192
32684
  if (isVendoredShadcnUiFilename(context.filename)) return {};
33193
32685
  return deprecatedReactImportRule.create(buildOncePerApiContext(context));
@@ -33511,11 +33003,34 @@ const noRedundantShouldComponentUpdate = defineRule({
33511
33003
  });
33512
33004
  //#endregion
33513
33005
  //#region src/plugin/rules/architecture/no-render-in-render.ts
33006
+ const tracesToPropOrParameter = (symbol, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
33007
+ if (!symbol || visitedSymbols.has(symbol)) return false;
33008
+ visitedSymbols.add(symbol);
33009
+ if (isComponentParameterSymbol(symbol)) return true;
33010
+ if (!isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
33011
+ const source = symbol.initializer;
33012
+ if (!source) return false;
33013
+ return initializerRootsInProps(source, scopes, visitedSymbols);
33014
+ };
33015
+ const initializerRootsInProps = (node, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
33016
+ if (isNodeOfType(node, "LogicalExpression")) return initializerRootsInProps(node.left, scopes, visitedSymbols) || initializerRootsInProps(node.right, scopes, visitedSymbols);
33017
+ if (isNodeOfType(node, "ConditionalExpression")) return initializerRootsInProps(node.consequent, scopes, visitedSymbols) || initializerRootsInProps(node.alternate, scopes, visitedSymbols);
33018
+ return rootsInProps(node, scopes, visitedSymbols);
33019
+ };
33020
+ const rootsInProps = (node, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
33021
+ let current = node;
33022
+ while (isNodeOfType(current, "MemberExpression")) {
33023
+ if (isNodeOfType(current.object, "ThisExpression") && isNodeOfType(current.property, "Identifier") && current.property.name === "props") return true;
33024
+ current = current.object;
33025
+ }
33026
+ if (isNodeOfType(current, "Identifier")) return tracesToPropOrParameter(scopes.symbolFor(current), scopes, visitedSymbols);
33027
+ return false;
33028
+ };
33514
33029
  const isInsideComponentContext = (node) => {
33515
33030
  let cursor = node.parent;
33516
33031
  while (cursor) {
33032
+ if (isNodeOfType(cursor, "ClassDeclaration") || isNodeOfType(cursor, "ClassExpression")) return true;
33517
33033
  if (isFunctionLike$1(cursor) && isComponentFunction$1(cursor)) return true;
33518
- if (isEs5Component(cursor) || isEs6Component(cursor)) return true;
33519
33034
  cursor = cursor.parent ?? null;
33520
33035
  }
33521
33036
  return false;
@@ -33525,28 +33040,24 @@ const functionBodyOf = (node) => {
33525
33040
  if (isNodeOfType(node, "VariableDeclarator") && node.init && isFunctionLike$1(node.init)) return node.init.body ?? null;
33526
33041
  return null;
33527
33042
  };
33528
- const isHookCallee = (callee) => {
33529
- if (isNodeOfType(callee, "Identifier")) return isReactHookName(callee.name);
33530
- if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.object, "Identifier") && isUppercaseName(callee.object.name) && isNodeOfType(callee.property, "Identifier")) return isReactHookName(callee.property.name);
33531
- return false;
33532
- };
33533
33043
  const containsHookCall = (body) => {
33534
33044
  let found = false;
33535
33045
  walkAst(body, (child) => {
33536
- if (found) return false;
33537
- if (child !== body && isFunctionLike$1(child) && isComponentFunction$1(child)) return false;
33046
+ if (found) return;
33538
33047
  if (!isNodeOfType(child, "CallExpression")) return;
33539
- if (isHookCallee(child.callee)) found = true;
33048
+ const name = getCalleeName$2(child);
33049
+ if (name && isReactHookName(name)) found = true;
33540
33050
  });
33541
33051
  return found;
33542
33052
  };
33543
- const isHookCallingRenderHelper = (symbol) => {
33053
+ const isModuleScopeHookFreeHelper = (symbol) => {
33544
33054
  if (!symbol) return false;
33545
33055
  const declaration = symbol.declarationNode;
33546
33056
  if (!isNodeOfType(declaration, "FunctionDeclaration") && !isNodeOfType(declaration, "VariableDeclarator")) return false;
33547
33057
  const body = functionBodyOf(declaration);
33548
33058
  if (!body) return false;
33549
- return containsHookCall(body);
33059
+ if (findEnclosingFunction(declaration) !== null) return false;
33060
+ return !containsHookCall(body);
33550
33061
  };
33551
33062
  const noRenderInRender = defineRule({
33552
33063
  id: "no-render-in-render",
@@ -33555,13 +33066,20 @@ const noRenderInRender = defineRule({
33555
33066
  tags: ["test-noise"],
33556
33067
  recommendation: "Make it a named component rendered as JSX so React can track it and preserve its state.",
33557
33068
  create: (context) => ({ JSXExpressionContainer(node) {
33558
- const expression = isNodeOfType(node.expression, "ChainExpression") ? node.expression.expression : node.expression;
33069
+ const expression = node.expression;
33559
33070
  if (!isNodeOfType(expression, "CallExpression")) return;
33560
- if (!isNodeOfType(expression.callee, "Identifier")) return;
33561
- const calleeName = expression.callee.name;
33562
- if (!RENDER_FUNCTION_PATTERN.test(calleeName)) return;
33071
+ let calleeName = null;
33072
+ if (isNodeOfType(expression.callee, "Identifier")) calleeName = expression.callee.name;
33073
+ else if (isNodeOfType(expression.callee, "MemberExpression") && isNodeOfType(expression.callee.property, "Identifier")) calleeName = expression.callee.property.name;
33074
+ if (!calleeName || !RENDER_FUNCTION_PATTERN.test(calleeName)) return;
33563
33075
  if (!isInsideComponentContext(node)) return;
33564
- if (!isHookCallingRenderHelper(context.scopes.symbolFor(expression.callee))) return;
33076
+ if (isNodeOfType(expression.callee, "Identifier")) {
33077
+ const calleeSymbol = context.scopes.symbolFor(expression.callee);
33078
+ if (tracesToPropOrParameter(calleeSymbol, context.scopes)) return;
33079
+ if (isModuleScopeHookFreeHelper(calleeSymbol)) return;
33080
+ } else if (isNodeOfType(expression.callee, "MemberExpression")) {
33081
+ if (rootsInProps(expression.callee.object, context.scopes)) return;
33082
+ }
33565
33083
  context.report({
33566
33084
  node: expression,
33567
33085
  message: `"${calleeName}()" hides a component behind an inline call, so pull it into its own component and render it as JSX so React can track it.`
@@ -33622,7 +33140,13 @@ const noRenderPropChildren = defineRule({
33622
33140
  //#endregion
33623
33141
  //#region src/plugin/rules/react-builtins/no-render-return-value.ts
33624
33142
  const MESSAGE$14 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
33625
- const isReactDomRenderCall = (node) => isGlobalMethodCall(node, "ReactDOM", "render");
33143
+ const isReactDomRenderCall = (node) => {
33144
+ if (!isNodeOfType(node.callee, "MemberExpression")) return false;
33145
+ if (!isNodeOfType(node.callee.object, "Identifier")) return false;
33146
+ if (node.callee.object.name !== "ReactDOM") return false;
33147
+ if (!isNodeOfType(node.callee.property, "Identifier")) return false;
33148
+ return node.callee.property.name === "render";
33149
+ };
33626
33150
  const isUsedAsReturnValue = (parent) => {
33627
33151
  if (!parent) return false;
33628
33152
  if (isNodeOfType(parent, "VariableDeclarator") || isNodeOfType(parent, "Property") || isNodeOfType(parent, "ReturnStatement") || isNodeOfType(parent, "AssignmentExpression")) return true;
@@ -34458,7 +33982,7 @@ const noSetState = defineRule({
34458
33982
  category: "Architecture",
34459
33983
  create: (context) => ({ CallExpression(node) {
34460
33984
  if (!isNodeOfType(node.callee, "MemberExpression")) return;
34461
- if (!isNodeOfType(stripParenExpression(node.callee.object), "ThisExpression")) return;
33985
+ if (!isNodeOfType(node.callee.object, "ThisExpression")) return;
34462
33986
  if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
34463
33987
  if (!getParentComponent(node)) return;
34464
33988
  context.report({
@@ -36904,36 +36428,6 @@ const isTriviallyCheapExpression = (node) => {
36904
36428
  if (isNodeOfType(innerExpression, "MemberExpression")) return false;
36905
36429
  return true;
36906
36430
  };
36907
- const isTrivialContainerLiteral = (node) => {
36908
- if (!node) return false;
36909
- const innerExpression = stripParenExpression(node);
36910
- if (isNodeOfType(innerExpression, "ArrayExpression")) return (innerExpression.elements ?? []).every((element) => element !== null && isSimpleExpression(element));
36911
- if (isNodeOfType(innerExpression, "ObjectExpression")) return (innerExpression.properties ?? []).every((property) => isNodeOfType(property, "Property") && !property.computed && isSimpleExpression(property.value));
36912
- return false;
36913
- };
36914
- const isNonEscapingRead = (identifier) => {
36915
- const readRoot = findTransparentExpressionRoot(identifier);
36916
- const memberNode = readRoot.parent;
36917
- if (!isNodeOfType(memberNode, "MemberExpression") || memberNode.object !== readRoot) return false;
36918
- const memberUse = findTransparentExpressionRoot(memberNode);
36919
- const memberUseParent = memberUse.parent;
36920
- if (isNodeOfType(memberUseParent, "AssignmentExpression") && memberUseParent.left === memberUse) return false;
36921
- if (isNodeOfType(memberUseParent, "UpdateExpression")) return false;
36922
- if (isNodeOfType(memberUseParent, "UnaryExpression") && memberUseParent.operator === "delete") return false;
36923
- return !(isNodeOfType(memberUseParent, "CallExpression") && memberUseParent.callee === memberUse && !memberNode.computed && isNodeOfType(memberNode.property, "Identifier") && MUTATING_ARRAY_METHODS.has(memberNode.property.name));
36924
- };
36925
- const isMemoIdentityUnused = (memoCallNode, scopes) => {
36926
- const memoUsageRoot = findTransparentExpressionRoot(memoCallNode);
36927
- const parentNode = memoUsageRoot.parent;
36928
- if (isNodeOfType(parentNode, "ExpressionStatement")) return true;
36929
- if (!isNodeOfType(parentNode, "VariableDeclarator") || parentNode.init !== memoUsageRoot) return false;
36930
- const bindingTarget = parentNode.id;
36931
- if (isNodeOfType(bindingTarget, "ArrayPattern") || isNodeOfType(bindingTarget, "ObjectPattern")) return true;
36932
- if (!isNodeOfType(bindingTarget, "Identifier")) return false;
36933
- const symbol = scopes.symbolFor(bindingTarget);
36934
- if (!symbol) return false;
36935
- return symbol.references.every((reference) => reference.flag === "read" && isNonEscapingRead(reference.identifier));
36936
- };
36937
36431
  const noUsememoSimpleExpression = defineRule({
36938
36432
  id: "no-usememo-simple-expression",
36939
36433
  title: "useMemo on a cheap value",
@@ -36955,17 +36449,9 @@ const noUsememoSimpleExpression = defineRule({
36955
36449
  let returnExpression = null;
36956
36450
  if (!isNodeOfType(callback.body, "BlockStatement")) returnExpression = callback.body;
36957
36451
  else if (callback.body.body?.length === 1 && isNodeOfType(callback.body.body[0], "ReturnStatement")) returnExpression = callback.body.body[0].argument;
36958
- if (!returnExpression) return;
36959
- if (isTriviallyCheapExpression(returnExpression)) {
36960
- context.report({
36961
- node,
36962
- message: "This costs more than it saves because useMemo is wrapping a value that's already cheap, so remove the useMemo"
36963
- });
36964
- return;
36965
- }
36966
- if (isTrivialContainerLiteral(returnExpression) && isMemoIdentityUnused(node, context.scopes)) context.report({
36452
+ if (returnExpression && isTriviallyCheapExpression(returnExpression)) context.report({
36967
36453
  node,
36968
- message: "This useMemo rebuilds a tiny literal whose reference is never relied on, so remove the useMemo and build the value inline"
36454
+ message: "This costs more than it saves because useMemo is wrapping a value that's already cheap, so remove the useMemo"
36969
36455
  });
36970
36456
  } })
36971
36457
  });
@@ -37071,7 +36557,7 @@ const noWillUpdateSetState = defineRule({
37071
36557
  const activeLifecycleNames = isReactBelow16_3(context.settings) ? new Set(["componentWillUpdate"]) : LIFECYCLE_NAMES;
37072
36558
  return { CallExpression(node) {
37073
36559
  if (!isNodeOfType(node.callee, "MemberExpression")) return;
37074
- if (!isNodeOfType(stripParenExpression(node.callee.object), "ThisExpression")) return;
36560
+ if (!isNodeOfType(node.callee.object, "ThisExpression")) return;
37075
36561
  if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
37076
36562
  if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
37077
36563
  context.report({
@@ -37388,10 +36874,6 @@ const isRouteFactoryCall = (expression) => {
37388
36874
  }
37389
36875
  return false;
37390
36876
  };
37391
- const isConfigOnlyFactoryCall = (call) => call.arguments.every((argument) => {
37392
- const expression = skipTsExpression(argument);
37393
- return isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "Literal") || isNodeOfType(expression, "TemplateLiteral");
37394
- });
37395
36877
  const isReactHocName = (name, state) => state.customHocs.has(name);
37396
36878
  const isHocCallee = (callee, state) => {
37397
36879
  if (isNodeOfType(callee, "Identifier")) return isReactHocName(callee.name, state);
@@ -37431,7 +36913,7 @@ const objectExpressionBundlesComponents = (objectExpression, state) => {
37431
36913
  continue;
37432
36914
  }
37433
36915
  if (!(!property.computed && isNodeOfType(property.key, "Identifier") && isReactComponentName(property.key.name))) continue;
37434
- if ((isNodeOfType(value, "ArrowFunctionExpression") || isNodeOfType(value, "FunctionExpression")) && functionContainsReactRenderOutput(value, state.scopes)) return true;
36916
+ if (isNodeOfType(value, "ArrowFunctionExpression") || isNodeOfType(value, "FunctionExpression")) return true;
37435
36917
  if (isNodeOfType(value, "CallExpression") && isHocCallee(value.callee, state) && value.arguments.length > 0) return true;
37436
36918
  }
37437
36919
  return false;
@@ -37539,22 +37021,18 @@ const onlyExportComponents = defineRule({
37539
37021
  customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
37540
37022
  allowExportNames: new Set(settings.allowExportNames),
37541
37023
  allowConstantExport: settings.allowConstantExport,
37542
- localComponentNames,
37543
- scopes: context.scopes
37024
+ localComponentNames
37544
37025
  };
37545
37026
  for (const child of componentCandidates) {
37546
37027
  if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
37547
- if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child) && functionContainsReactRenderOutput(child, context.scopes)) localComponentNames.add(child.id.name);
37028
+ if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
37548
37029
  }
37549
37030
  if (isNodeOfType(child, "ClassDeclaration") && child.id) {
37550
37031
  if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
37551
37032
  }
37552
37033
  if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
37553
37034
  const initializer = child.init;
37554
- if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child)) {
37555
- const expression = initializer ? skipTsExpression(initializer) : null;
37556
- if (!(expression !== null && (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression"))) || functionContainsReactRenderOutput(expression, context.scopes)) localComponentNames.add(child.id.name);
37557
- }
37035
+ if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
37558
37036
  }
37559
37037
  }
37560
37038
  const exports = [];
@@ -37624,10 +37102,6 @@ const onlyExportComponents = defineRule({
37624
37102
  return false;
37625
37103
  })();
37626
37104
  if (isHoc && firstArgIsValid) hasReactExport = true;
37627
- else if (!isHoc && isConfigOnlyFactoryCall(stripped)) exports.push({
37628
- kind: "non-component",
37629
- reportNode: stripped
37630
- });
37631
37105
  else context.report({
37632
37106
  node: stripped,
37633
37107
  message: ANONYMOUS_MESSAGE
@@ -39223,22 +38697,11 @@ const getSubscriptionHandlerArgument = (subscribeCall, effectBodyStatements) =>
39223
38697
  }
39224
38698
  return null;
39225
38699
  };
39226
- const isTrivialLiteralExpression = (expression) => {
39227
- if (isNodeOfType(expression, "Literal")) return true;
39228
- if (isNodeOfType(expression, "Identifier")) return expression.name === "undefined";
39229
- if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "-") return isNodeOfType(expression.argument, "Literal");
39230
- if (isNodeOfType(expression, "TemplateLiteral")) return (expression.expressions?.length ?? 0) === 0;
39231
- return false;
39232
- };
39233
38700
  const getSingleSetterCallFromHandler = (handler) => {
39234
38701
  const handlerStatements = getCallbackStatements(handler);
39235
38702
  if (handlerStatements.length !== 1) return null;
39236
38703
  const onlyStatement = handlerStatements[0];
39237
- let expression = onlyStatement;
39238
- if (isNodeOfType(onlyStatement, "ExpressionStatement")) expression = onlyStatement.expression;
39239
- if (isNodeOfType(onlyStatement, "ReturnStatement")) expression = onlyStatement.argument;
39240
- if (!expression) return null;
39241
- expression = stripParenExpression(expression);
38704
+ const expression = isNodeOfType(onlyStatement, "ExpressionStatement") ? onlyStatement.expression : onlyStatement;
39242
38705
  if (!isNodeOfType(expression, "CallExpression")) return null;
39243
38706
  if (!isNodeOfType(expression.callee, "Identifier")) return null;
39244
38707
  if (!isSetterIdentifier(expression.callee.name)) return null;
@@ -39248,106 +38711,6 @@ const getSingleSetterCallFromHandler = (handler) => {
39248
38711
  setterArgument: expression.arguments[0]
39249
38712
  };
39250
38713
  };
39251
- const isListenerCollectionInitializer = (init) => {
39252
- if (!init) return false;
39253
- if (isNodeOfType(init, "ArrayExpression")) return true;
39254
- return isNodeOfType(init, "NewExpression") && isNodeOfType(init.callee, "Identifier") && init.callee.name === "Set";
39255
- };
39256
- const functionRegistersParameterIntoCollection = (functionNode, listenerCollectionNames) => {
39257
- if (!isFunctionLike$1(functionNode)) return false;
39258
- const firstParam = functionNode.params?.[0];
39259
- if (!isNodeOfType(firstParam, "Identifier")) return false;
39260
- const listenerParamName = firstParam.name;
39261
- let registersListener = false;
39262
- walkAst(functionNode.body, (child) => {
39263
- if (registersListener) return false;
39264
- if (!isNodeOfType(child, "CallExpression")) return;
39265
- if (!isNodeOfType(child.callee, "MemberExpression")) return;
39266
- if (!isNodeOfType(child.callee.object, "Identifier")) return;
39267
- if (!listenerCollectionNames.has(child.callee.object.name)) return;
39268
- if (!isNodeOfType(child.callee.property, "Identifier")) return;
39269
- if (child.callee.property.name !== "add" && child.callee.property.name !== "push") return;
39270
- const registeredArgument = child.arguments?.[0];
39271
- if (isNodeOfType(registeredArgument, "Identifier") && registeredArgument.name === listenerParamName) registersListener = true;
39272
- });
39273
- return registersListener;
39274
- };
39275
- const buildModuleScopeStoreIndex = (programRoot) => {
39276
- const mutableBindingNames = /* @__PURE__ */ new Set();
39277
- const listenerCollectionNames = /* @__PURE__ */ new Set();
39278
- const moduleFunctionsByName = /* @__PURE__ */ new Map();
39279
- if (!isNodeOfType(programRoot, "Program")) return {
39280
- mutableBindingNames,
39281
- subscribeFunctionNames: /* @__PURE__ */ new Set()
39282
- };
39283
- for (const statement of programRoot.body ?? []) {
39284
- const unwrapped = isNodeOfType(statement, "ExportNamedDeclaration") && statement.declaration ? statement.declaration : statement;
39285
- if (isNodeOfType(unwrapped, "FunctionDeclaration") && unwrapped.id) {
39286
- moduleFunctionsByName.set(unwrapped.id.name, unwrapped);
39287
- continue;
39288
- }
39289
- if (!isNodeOfType(unwrapped, "VariableDeclaration")) continue;
39290
- for (const declarator of unwrapped.declarations ?? []) {
39291
- if (!isNodeOfType(declarator.id, "Identifier")) continue;
39292
- const init = declarator.init ?? null;
39293
- if ((unwrapped.kind === "let" || unwrapped.kind === "var") && init && !isFunctionLike$1(init)) {
39294
- mutableBindingNames.add(declarator.id.name);
39295
- continue;
39296
- }
39297
- if (isListenerCollectionInitializer(init)) {
39298
- listenerCollectionNames.add(declarator.id.name);
39299
- continue;
39300
- }
39301
- if (init && isFunctionLike$1(init)) moduleFunctionsByName.set(declarator.id.name, init);
39302
- }
39303
- }
39304
- const subscribeFunctionNames = /* @__PURE__ */ new Set();
39305
- for (const [functionName, functionNode] of moduleFunctionsByName) if (functionRegistersParameterIntoCollection(functionNode, listenerCollectionNames)) subscribeFunctionNames.add(functionName);
39306
- return {
39307
- mutableBindingNames,
39308
- subscribeFunctionNames
39309
- };
39310
- };
39311
- const getModuleStoreSnapshotName = (useStateCall, storeIndex) => {
39312
- if (!isNodeOfType(useStateCall, "CallExpression")) return null;
39313
- let initialArgument = stripParenExpression(useStateCall.arguments?.[0]);
39314
- if (initialArgument && isFunctionLike$1(initialArgument) && !isNodeOfType(initialArgument.body, "BlockStatement")) initialArgument = stripParenExpression(initialArgument.body);
39315
- if (!isNodeOfType(initialArgument, "Identifier")) return null;
39316
- if (!storeIndex.mutableBindingNames.has(initialArgument.name)) return null;
39317
- const binding = findVariableInitializer(initialArgument, initialArgument.name);
39318
- if (!binding || !isNodeOfType(binding.scopeOwner, "Program")) return null;
39319
- return initialArgument.name;
39320
- };
39321
- const argumentForwardsSetter = (argument, setterName) => {
39322
- if (!argument) return false;
39323
- const unwrapped = stripParenExpression(argument);
39324
- if (isNodeOfType(unwrapped, "Identifier")) return unwrapped.name === setterName;
39325
- if (!isFunctionLike$1(unwrapped)) return false;
39326
- let callsSetter = false;
39327
- walkAst(unwrapped.body, (child) => {
39328
- if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && child.callee.name === setterName) {
39329
- callsSetter = true;
39330
- return false;
39331
- }
39332
- });
39333
- return callsSetter;
39334
- };
39335
- const findModuleSubscribeCallForwardingSetter = (effectCallback, setterName, storeIndex) => {
39336
- let matchedCall = null;
39337
- walkAst((isFunctionLike$1(effectCallback) ? effectCallback.body : null) ?? effectCallback, (child) => {
39338
- if (matchedCall) return false;
39339
- if (!isNodeOfType(child, "CallExpression")) return;
39340
- if (!isNodeOfType(child.callee, "Identifier")) return;
39341
- if (!storeIndex.subscribeFunctionNames.has(child.callee.name)) return;
39342
- const binding = findVariableInitializer(child.callee, child.callee.name);
39343
- if (!binding || !isNodeOfType(binding.scopeOwner, "Program")) return;
39344
- for (const argument of child.arguments ?? []) if (argumentForwardsSetter(argument, setterName)) {
39345
- matchedCall = child;
39346
- return false;
39347
- }
39348
- });
39349
- return matchedCall;
39350
- };
39351
38714
  const cleanupReleasesSubscription = (effectBodyStatements, boundReleaseName, boundSubscriptionName) => {
39352
38715
  const lastStatement = effectBodyStatements[effectBodyStatements.length - 1];
39353
38716
  if (!isNodeOfType(lastStatement, "ReturnStatement")) return false;
@@ -39364,16 +38727,6 @@ const preferUseSyncExternalStore = defineRule({
39364
38727
  severity: "warn",
39365
38728
  recommendation: "Replace the `useState(getSnapshot())` + `useEffect(() => store.subscribe(() => setSnapshot(getSnapshot())))` pair with `useSyncExternalStore(store.subscribe, getSnapshot)`. The hook gets this right during concurrent rendering and on the server; the hand-rolled version doesn't.",
39366
38729
  create: (context) => {
39367
- let cachedStoreIndex = null;
39368
- const storeIndexFor = (node) => {
39369
- if (cachedStoreIndex) return cachedStoreIndex;
39370
- const programRoot = findProgramRoot(node);
39371
- cachedStoreIndex = programRoot ? buildModuleScopeStoreIndex(programRoot) : {
39372
- mutableBindingNames: /* @__PURE__ */ new Set(),
39373
- subscribeFunctionNames: /* @__PURE__ */ new Set()
39374
- };
39375
- return cachedStoreIndex;
39376
- };
39377
38730
  const checkComponent = (componentBody) => {
39378
38731
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
39379
38732
  const useStateBindings = collectUseStateBindings(componentBody);
@@ -39411,7 +38764,6 @@ const preferUseSyncExternalStore = defineRule({
39411
38764
  const useStateInitializer = useStateInitializerByValueName.get(valueName);
39412
38765
  if (!useStateInitializer) continue;
39413
38766
  if (!areExpressionsStructurallyEqual(useStateInitializer, setterPayload.setterArgument)) continue;
39414
- if (isTrivialLiteralExpression(setterPayload.setterArgument)) continue;
39415
38767
  if (!cleanupReleasesSubscription(effectBodyStatements, subscription.boundReleaseName, subscription.boundSubscriptionName)) continue;
39416
38768
  const matchingBinding = useStateBindings.find((binding) => binding.valueName === valueName);
39417
38769
  context.report({
@@ -39419,47 +38771,14 @@ const preferUseSyncExternalStore = defineRule({
39419
38771
  message: `Your users can see stale or torn values because useState "${valueName}" syncs an outside store through a useEffect.`
39420
38772
  });
39421
38773
  }
39422
- checkModuleStoreShape(componentBody, useStateBindings);
39423
- };
39424
- const checkModuleStoreShape = (componentBody, useStateBindings) => {
39425
- const storeIndex = storeIndexFor(componentBody);
39426
- if (storeIndex.mutableBindingNames.size === 0) return;
39427
- if (storeIndex.subscribeFunctionNames.size === 0) return;
39428
- const snapshotBindings = useStateBindings.map((binding) => ({
39429
- binding,
39430
- storeName: isNodeOfType(binding.declarator.init, "CallExpression") ? getModuleStoreSnapshotName(binding.declarator.init, storeIndex) : null
39431
- })).filter((candidate) => candidate.storeName !== null);
39432
- if (snapshotBindings.length === 0) return;
39433
- const reportedDeclarators = /* @__PURE__ */ new Set();
39434
- for (const effectCall of findUseEffectsInComponent(componentBody)) {
39435
- if (!isNodeOfType(effectCall, "CallExpression")) continue;
39436
- if ((effectCall.arguments?.length ?? 0) < 2) continue;
39437
- const depsNode = effectCall.arguments[1];
39438
- if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
39439
- if ((depsNode.elements?.length ?? 0) !== 0) continue;
39440
- const callback = getEffectCallback(effectCall);
39441
- if (!callback || !isFunctionLike$1(callback)) continue;
39442
- for (const { binding, storeName } of snapshotBindings) {
39443
- if (reportedDeclarators.has(binding.declarator)) continue;
39444
- if (!findModuleSubscribeCallForwardingSetter(callback, binding.setterName, storeIndex)) continue;
39445
- reportedDeclarators.add(binding.declarator);
39446
- context.report({
39447
- node: binding.declarator,
39448
- message: `Your users can miss updates or see torn values because useState "${binding.valueName}" snapshots module store "${storeName}" at render but only subscribes later in a useEffect.`
39449
- });
39450
- }
39451
- }
39452
38774
  };
39453
38775
  return {
39454
38776
  FunctionDeclaration(node) {
39455
- const functionName = node.id?.name;
39456
- if (!functionName) return;
39457
- if (!isUppercaseName(functionName) && !isReactHookName(functionName)) return;
38777
+ if (!node.id?.name || !isUppercaseName(node.id.name)) return;
39458
38778
  checkComponent(node.body);
39459
38779
  },
39460
38780
  VariableDeclarator(node) {
39461
- const isHookAssignment = isNodeOfType(node.id, "Identifier") && isReactHookName(node.id.name);
39462
- if (!isComponentAssignment(node) && !isHookAssignment) return;
38781
+ if (!isComponentAssignment(node)) return;
39463
38782
  if (!isNodeOfType(node.init, "ArrowFunctionExpression") && !isNodeOfType(node.init, "FunctionExpression")) return;
39464
38783
  checkComponent(node.init.body);
39465
38784
  }
@@ -40069,7 +39388,7 @@ const queryNoVoidQueryFn = defineRule({
40069
39388
  if (isNodeOfType(queryFnValue, "ArrowFunctionExpression") || isNodeOfType(queryFnValue, "FunctionExpression")) {
40070
39389
  const body = queryFnValue.body;
40071
39390
  if (!isNodeOfType(body, "BlockStatement")) return;
40072
- if ((body.body ?? []).filter((statement) => !isNoOpStatement(statement)).length === 0) context.report({
39391
+ if ((body.body ?? []).length === 0) context.report({
40073
39392
  node: queryFnProperty,
40074
39393
  message: "This empty queryFn caches undefined, so the component never gets data."
40075
39394
  });
@@ -40576,6 +39895,17 @@ const renderingHoistJsx = defineRule({
40576
39895
  }
40577
39896
  });
40578
39897
  //#endregion
39898
+ //#region src/plugin/utils/find-declarator-for-binding.ts
39899
+ const findDeclaratorForBinding = (bindingIdentifier) => {
39900
+ let ancestor = bindingIdentifier.parent;
39901
+ while (ancestor) {
39902
+ if (isNodeOfType(ancestor, "VariableDeclarator")) return ancestor;
39903
+ if (isNodeOfType(ancestor, "FunctionDeclaration") || isNodeOfType(ancestor, "FunctionExpression") || isNodeOfType(ancestor, "ArrowFunctionExpression") || isNodeOfType(ancestor, "Program")) return null;
39904
+ ancestor = ancestor.parent ?? null;
39905
+ }
39906
+ return null;
39907
+ };
39908
+ //#endregion
40579
39909
  //#region src/plugin/rules/performance/rendering-hydration-mismatch-time.ts
40580
39910
  const NONDETERMINISTIC_RENDER_PATTERNS = [
40581
39911
  {
@@ -40584,19 +39914,19 @@ const NONDETERMINISTIC_RENDER_PATTERNS = [
40584
39914
  },
40585
39915
  {
40586
39916
  display: "Date.now()",
40587
- matches: (node) => isGlobalMethodCall(node, "Date", "now")
39917
+ matches: (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && node.callee.object.name === "Date" && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "now"
40588
39918
  },
40589
39919
  {
40590
39920
  display: "Math.random()",
40591
- matches: (node) => isGlobalMethodCall(node, "Math", "random")
39921
+ matches: (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && node.callee.object.name === "Math" && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "random"
40592
39922
  },
40593
39923
  {
40594
39924
  display: "performance.now()",
40595
- matches: (node) => isGlobalMethodCall(node, "performance", "now")
39925
+ matches: (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && node.callee.object.name === "performance" && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "now"
40596
39926
  },
40597
39927
  {
40598
39928
  display: "crypto.randomUUID()",
40599
- matches: (node) => isGlobalMethodCall(node, "crypto", "randomUUID")
39929
+ matches: (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && node.callee.object.name === "crypto" && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "randomUUID"
40600
39930
  }
40601
39931
  ];
40602
39932
  const findOpeningElementOfChild = (jsxNode) => {
@@ -40608,6 +39938,54 @@ const findOpeningElementOfChild = (jsxNode) => {
40608
39938
  }
40609
39939
  return null;
40610
39940
  };
39941
+ const executesDuringRender = (functionNode) => {
39942
+ const parent = functionNode.parent;
39943
+ if (!isNodeOfType(parent, "CallExpression")) return false;
39944
+ if (parent.callee === functionNode) return true;
39945
+ return isHookCall$2(parent, "useMemo") && parent.arguments?.[0] === functionNode;
39946
+ };
39947
+ const CLIENT_ONLY_FLAG_NAME_PATTERN = /^(?:is|has|did)?_?(?:client|mounted|hydrated|browser)(?:_?(?:side|ready|only))?$/i;
39948
+ const referencesClientOnlyFlag = (expression) => {
39949
+ const unwrapped = stripParenExpression(expression);
39950
+ if (isNodeOfType(unwrapped, "Identifier")) return CLIENT_ONLY_FLAG_NAME_PATTERN.test(unwrapped.name);
39951
+ if (isNodeOfType(unwrapped, "MemberExpression")) {
39952
+ const property = unwrapped.property;
39953
+ return isNodeOfType(property, "Identifier") && CLIENT_ONLY_FLAG_NAME_PATTERN.test(property.name);
39954
+ }
39955
+ if (isNodeOfType(unwrapped, "UnaryExpression") && unwrapped.operator === "!") return referencesClientOnlyFlag(unwrapped.argument);
39956
+ if (isNodeOfType(unwrapped, "LogicalExpression")) return referencesClientOnlyFlag(unwrapped.left) || referencesClientOnlyFlag(unwrapped.right);
39957
+ return false;
39958
+ };
39959
+ const isFalsyLiteral = (node) => {
39960
+ if (!node) return true;
39961
+ if (isNodeOfType(node, "Literal")) return !node.value;
39962
+ return isNodeOfType(node, "Identifier") && node.name === "undefined";
39963
+ };
39964
+ const isFalsyInitialStateBinding = (identifier) => {
39965
+ if (!isNodeOfType(identifier, "Identifier")) return false;
39966
+ const binding = findVariableInitializer(identifier, identifier.name);
39967
+ if (!binding) return false;
39968
+ const declarator = findDeclaratorForBinding(binding.bindingIdentifier);
39969
+ if (!declarator?.init) return false;
39970
+ const init = stripParenExpression(declarator.init);
39971
+ if (!isHookCall$2(init, "useState") || !isNodeOfType(init, "CallExpression")) return false;
39972
+ if (!isNodeOfType(declarator.id, "ArrayPattern")) return false;
39973
+ if (declarator.id.elements?.[0] !== binding.bindingIdentifier) return false;
39974
+ return isFalsyLiteral(init.arguments?.[0]);
39975
+ };
39976
+ const referencesFalsyInitialState = (expression) => flattenLogicalAndChain(stripParenExpression(expression)).some((operand) => isFalsyInitialStateBinding(stripParenExpression(operand)));
39977
+ const isGatedByFalsyInitialState = (node) => {
39978
+ let cursor = node;
39979
+ let parent = node.parent;
39980
+ while (parent) {
39981
+ if (isNodeOfType(parent, "LogicalExpression") && parent.operator === "&&" && parent.right === cursor && referencesFalsyInitialState(parent.left)) return true;
39982
+ if (isNodeOfType(parent, "ConditionalExpression") && parent.consequent === cursor && referencesFalsyInitialState(parent.test)) return true;
39983
+ if (isNodeOfType(parent, "IfStatement") && parent.consequent === cursor && referencesFalsyInitialState(parent.test)) return true;
39984
+ cursor = parent;
39985
+ parent = parent.parent ?? null;
39986
+ }
39987
+ return false;
39988
+ };
40611
39989
  const isYearOnlyDateRead = (dateNode) => {
40612
39990
  const member = dateNode.parent;
40613
39991
  if (!isNodeOfType(member, "MemberExpression") || member.object !== dateNode) return false;
@@ -40631,6 +40009,23 @@ const isInsideMotionTransitionAttribute = (node) => {
40631
40009
  }
40632
40010
  return false;
40633
40011
  };
40012
+ const isInsideClientOnlyGuard = (node) => {
40013
+ let cursor = node;
40014
+ let parent = node.parent;
40015
+ while (parent) {
40016
+ if (isNodeOfType(parent, "LogicalExpression") && parent.operator === "&&" && parent.right === cursor && referencesClientOnlyFlag(parent.left)) return true;
40017
+ if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === cursor || parent.alternate === cursor) && referencesClientOnlyFlag(parent.test)) return true;
40018
+ if (isNodeOfType(parent, "IfStatement") && parent.consequent === cursor && referencesClientOnlyFlag(parent.test)) return true;
40019
+ cursor = parent;
40020
+ parent = parent.parent ?? null;
40021
+ }
40022
+ return false;
40023
+ };
40024
+ const hasSuppressHydrationWarningAttribute = (openingElement) => {
40025
+ if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement")) return false;
40026
+ for (const attr of openingElement.attributes ?? []) if (isNodeOfType(attr, "JSXAttribute") && isNodeOfType(attr.name, "JSXIdentifier") && attr.name.name === "suppressHydrationWarning") return true;
40027
+ return false;
40028
+ };
40634
40029
  const renderingHydrationMismatchTime = defineRule({
40635
40030
  id: "rendering-hydration-mismatch-time",
40636
40031
  title: "Time or random value in JSX",
@@ -40678,35 +40073,6 @@ const renderingHydrationMismatchTime = defineRule({
40678
40073
  }
40679
40074
  });
40680
40075
  //#endregion
40681
- //#region src/plugin/utils/contains-locale-environment-read.ts
40682
- const LOCALE_ENVIRONMENT_METHOD_NAMES = new Set([
40683
- "toLocaleString",
40684
- "toLocaleDateString",
40685
- "toLocaleTimeString",
40686
- "getTimezoneOffset"
40687
- ]);
40688
- const containsLocaleEnvironmentRead = (expression) => {
40689
- let readsLocaleEnvironment = false;
40690
- walkAst(expression, (child) => {
40691
- if (readsLocaleEnvironment) return false;
40692
- if (isNodeOfType(child, "MemberExpression") && isNodeOfType(child.object, "Identifier")) {
40693
- if (child.object.name === "Intl") {
40694
- readsLocaleEnvironment = true;
40695
- return false;
40696
- }
40697
- if (child.object.name === "navigator" && isNodeOfType(child.property, "Identifier") && (child.property.name === "language" || child.property.name === "languages")) {
40698
- readsLocaleEnvironment = true;
40699
- return false;
40700
- }
40701
- }
40702
- if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "MemberExpression") && !child.callee.computed && isNodeOfType(child.callee.property, "Identifier") && LOCALE_ENVIRONMENT_METHOD_NAMES.has(child.callee.property.name)) {
40703
- readsLocaleEnvironment = true;
40704
- return false;
40705
- }
40706
- });
40707
- return readsLocaleEnvironment;
40708
- };
40709
- //#endregion
40710
40076
  //#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
40711
40077
  const USE_EFFECT_ONLY = new Set(["useEffect"]);
40712
40078
  const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
@@ -40772,15 +40138,14 @@ const renderingHydrationNoFlicker = defineRule({
40772
40138
  if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
40773
40139
  const callback = getEffectCallback(node);
40774
40140
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
40775
- const bodyStatements = (isNodeOfType(callback.body, "BlockStatement") ? callback.body.body ?? [] : [callback.body]).filter((statement) => !isNoOpStatement(statement));
40776
- if (bodyStatements.length !== 1) return;
40141
+ const bodyStatements = isNodeOfType(callback.body, "BlockStatement") ? callback.body.body : [callback.body];
40142
+ if (!bodyStatements || bodyStatements.length !== 1) return;
40777
40143
  const soleStatement = bodyStatements[0];
40778
40144
  if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
40779
40145
  const expression = soleStatement.expression;
40780
40146
  if (isSetterCall(expression) && isNodeOfType(expression, "CallExpression") && isNodeOfType(expression.callee, "Identifier") && isUseStateSetterInScope(expression, expression.callee.name)) {
40781
40147
  if (argumentsReadRefCurrent(expression.arguments ?? [])) return;
40782
40148
  if (isStateUsedOnlyInIdOrAriaAttributes(expression, expression.callee.name)) return;
40783
- if ((expression.arguments ?? []).some(containsLocaleEnvironmentRead)) return;
40784
40149
  context.report({
40785
40150
  node,
40786
40151
  message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
@@ -41599,9 +40964,6 @@ const rerenderFunctionalSetstate = defineRule({
41599
40964
  } })
41600
40965
  });
41601
40966
  //#endregion
41602
- //#region src/plugin/utils/is-trivial-built-in-construction.ts
41603
- const isTrivialBuiltInConstruction = (expression) => isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && TRIVIAL_CONSTRUCTOR_NAMES.has(expression.callee.name) && (expression.arguments ?? []).length === 0;
41604
- //#endregion
41605
40967
  //#region src/plugin/rules/state-and-effects/rerender-lazy-ref-init.ts
41606
40968
  const rerenderLazyRefInit = defineRule({
41607
40969
  id: "rerender-lazy-ref-init",
@@ -41612,7 +40974,7 @@ const rerenderLazyRefInit = defineRule({
41612
40974
  recommendation: "Initialize the ref lazily so expensive values are not rebuilt and discarded on every render.",
41613
40975
  create: (context) => ({ CallExpression(node) {
41614
40976
  if (!isHookCall$2(node, "useRef") || !node.arguments?.length) return;
41615
- const initializer = stripParenExpression(node.arguments[0]);
40977
+ const initializer = node.arguments[0];
41616
40978
  const isPlainCall = isNodeOfType(initializer, "CallExpression");
41617
40979
  const isNewCall = isNodeOfType(initializer, "NewExpression");
41618
40980
  if (!isPlainCall && !isNewCall) return;
@@ -41620,7 +40982,7 @@ const rerenderLazyRefInit = defineRule({
41620
40982
  const memberPropertyName = isNodeOfType(callee, "MemberExpression") && (isNodeOfType(callee.property, "Identifier") || isNodeOfType(callee.property, "PrivateIdentifier")) ? callee.property.name : null;
41621
40983
  const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : memberPropertyName ?? "fn";
41622
40984
  if (TRIVIAL_INITIALIZER_NAMES.has(calleeName)) return;
41623
- if (isTrivialBuiltInConstruction(initializer)) return;
40985
+ if (isNewCall && TRIVIAL_CONSTRUCTOR_NAMES.has(calleeName)) return;
41624
40986
  if (isPlainCall && isReactHookName(calleeName)) return;
41625
40987
  const callShape = isNewCall ? `new ${calleeName}()` : `${calleeName}()`;
41626
40988
  context.report({
@@ -41656,11 +41018,11 @@ const TRIVIAL_DATE_GETTER_NAMES = new Set([
41656
41018
  const EAGER_CALL_RESOLUTION_DEPTH_LIMIT = 4;
41657
41019
  const findEagerInitializerCall = (expression, depth = 0) => {
41658
41020
  if (depth > EAGER_CALL_RESOLUTION_DEPTH_LIMIT) return null;
41659
- const innerExpression = stripParenExpression(expression);
41660
- if (isNodeOfType(innerExpression, "CallExpression") || isNodeOfType(innerExpression, "NewExpression")) return innerExpression;
41661
- if (isNodeOfType(innerExpression, "LogicalExpression")) return findEagerInitializerCall(innerExpression.left, depth + 1);
41662
- if (isNodeOfType(innerExpression, "MemberExpression")) return findEagerInitializerCall(innerExpression.object, depth + 1);
41663
- if (isNodeOfType(innerExpression, "ArrayExpression")) for (const element of innerExpression.elements ?? []) {
41021
+ if (isNodeOfType(expression, "CallExpression") || isNodeOfType(expression, "NewExpression")) return expression;
41022
+ if (isNodeOfType(expression, "ChainExpression")) return findEagerInitializerCall(expression.expression, depth + 1);
41023
+ if (isNodeOfType(expression, "LogicalExpression")) return findEagerInitializerCall(expression.left, depth + 1);
41024
+ if (isNodeOfType(expression, "MemberExpression")) return findEagerInitializerCall(expression.object, depth + 1);
41025
+ if (isNodeOfType(expression, "ArrayExpression")) for (const element of expression.elements ?? []) {
41664
41026
  if (!element || !isNodeOfType(element, "SpreadElement")) continue;
41665
41027
  const spreadCall = findEagerInitializerCall(element.argument, depth + 1);
41666
41028
  if (spreadCall) return spreadCall;
@@ -41683,7 +41045,7 @@ const rerenderLazyStateInit = defineRule({
41683
41045
  const memberPropertyName = isNodeOfType(callee, "MemberExpression") && (isNodeOfType(callee.property, "Identifier") || isNodeOfType(callee.property, "PrivateIdentifier")) ? callee.property.name : null;
41684
41046
  const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : memberPropertyName ?? "fn";
41685
41047
  if (TRIVIAL_INITIALIZER_NAMES.has(calleeName)) return;
41686
- if (isTrivialBuiltInConstruction(initializer)) return;
41048
+ if (isConstructor && TRIVIAL_CONSTRUCTOR_NAMES.has(calleeName)) return;
41687
41049
  if (memberPropertyName && (initializer.arguments ?? []).length === 0 && TRIVIAL_DATE_GETTER_NAMES.has(memberPropertyName)) return;
41688
41050
  if (isReactHookName(calleeName)) return;
41689
41051
  const callDescription = isConstructor ? `new ${calleeName}()` : `${calleeName}()`;
@@ -42350,7 +41712,7 @@ const rnAnimationReactionAsDerived = defineRule({
42350
41712
  const body = reactionFn.body;
42351
41713
  let singleAssignment = null;
42352
41714
  if (isNodeOfType(body, "BlockStatement")) {
42353
- const statements = (body.body ?? []).filter((statement) => !isNoOpStatement(statement));
41715
+ const statements = body.body ?? [];
42354
41716
  if (statements.length !== 1) return;
42355
41717
  const onlyStatement = statements[0];
42356
41718
  if (!isNodeOfType(onlyStatement, "ExpressionStatement")) return;
@@ -42424,8 +41786,7 @@ const PROMISE_SETTLE_METHODS = new Set([
42424
41786
  "catch",
42425
41787
  "finally"
42426
41788
  ]);
42427
- const findChainRoot = (wrappedNode) => {
42428
- const node = stripParenExpression(wrappedNode);
41789
+ const findChainRoot = (node) => {
42429
41790
  if (isNodeOfType(node, "CallExpression")) {
42430
41791
  if (isNodeOfType(node.callee, "Identifier")) return {
42431
41792
  calleeName: node.callee.name,
@@ -43013,21 +42374,20 @@ const isBindingReactNativeDimensions = (node, binding, localName) => {
43013
42374
  };
43014
42375
  const isReactNativeDimensionsCallee = (node, callee) => {
43015
42376
  if (!isNodeOfType(callee, "MemberExpression")) return false;
43016
- const receiver = stripParenExpression(callee.object);
43017
- if (isNodeOfType(receiver, "Identifier")) {
43018
- const localName = receiver.name;
42377
+ if (isNodeOfType(callee.object, "Identifier")) {
42378
+ const localName = callee.object.name;
43019
42379
  const binding = findVariableInitializer(node, localName);
43020
42380
  if (binding !== null) return isBindingReactNativeDimensions(node, binding, localName);
43021
42381
  return localName === "Dimensions";
43022
42382
  }
43023
- if (isNodeOfType(receiver, "MemberExpression") && !receiver.computed && isNodeOfType(receiver.property, "Identifier") && receiver.property.name === "Dimensions") {
43024
- if (getInitializerModuleSource(node, receiver.object) === REACT_NATIVE_MODULE) return true;
43025
- const rootName = getRootIdentifierName(receiver.object);
42383
+ if (isNodeOfType(callee.object, "MemberExpression") && !callee.object.computed && isNodeOfType(callee.object.property, "Identifier") && callee.object.property.name === "Dimensions") {
42384
+ if (getInitializerModuleSource(node, callee.object.object) === REACT_NATIVE_MODULE) return true;
42385
+ const rootName = getRootIdentifierName(callee.object.object);
43026
42386
  if (rootName === null) return false;
43027
42387
  const importBinding = getImportBindingForName(node, rootName);
43028
42388
  return importBinding !== null && importBinding.isNamespace && importBinding.source === REACT_NATIVE_MODULE;
43029
42389
  }
43030
- if (getRequireCallSource(receiver) === REACT_NATIVE_MODULE) return isNodeOfType(receiver, "MemberExpression") && !receiver.computed && isNodeOfType(receiver.property, "Identifier") && receiver.property.name === "Dimensions";
42390
+ if (getRequireCallSource(callee.object) === REACT_NATIVE_MODULE) return isNodeOfType(callee.object, "MemberExpression") && !callee.object.computed && isNodeOfType(callee.object.property, "Identifier") && callee.object.property.name === "Dimensions";
43031
42391
  return false;
43032
42392
  };
43033
42393
  const STYLE_FACTORY_CALLEE_PATTERN$1 = /(?:^|\.)(?:make|create)(?:Use)?Styles$/;
@@ -43471,8 +42831,7 @@ const rnNoLegacyShadowStyles = defineRule({
43471
42831
  },
43472
42832
  CallExpression(node) {
43473
42833
  if (!isNodeOfType(node.callee, "MemberExpression")) return;
43474
- const receiver = stripParenExpression(node.callee.object);
43475
- if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "StyleSheet") return;
42834
+ if (!isNodeOfType(node.callee.object, "Identifier") || node.callee.object.name !== "StyleSheet") return;
43476
42835
  if (!isMemberProperty(node.callee, "create")) return;
43477
42836
  const stylesArgument = node.arguments?.[0];
43478
42837
  if (!isNodeOfType(stylesArgument, "ObjectExpression")) return;
@@ -44322,7 +43681,7 @@ const rnNoSetNativeProps = defineRule({
44322
43681
  const callee = node.callee;
44323
43682
  if (!isStaticMemberNamed(callee, "setNativeProps")) return;
44324
43683
  if (!isNodeOfType(callee, "MemberExpression")) return;
44325
- if (!isStaticMemberNamed(stripParenExpression(callee.object), "current")) return;
43684
+ if (!isStaticMemberNamed(callee.object, "current")) return;
44326
43685
  context.report({
44327
43686
  node,
44328
43687
  message: "`setNativeProps` is a silent no-op under the New Architecture (Fabric), so this imperative update won't change the view. Drive the prop via state, an Animated.Value, or a Reanimated shared value."
@@ -44550,15 +43909,14 @@ const analyzeGestureChain = (expression) => {
44550
43909
  if (!isNodeOfType(callee, "MemberExpression")) return null;
44551
43910
  if (!isNodeOfType(callee.property, "Identifier")) return null;
44552
43911
  const methodName = callee.property.name;
44553
- const receiver = stripParenExpression(callee.object);
44554
- if (isNodeOfType(receiver, "Identifier") && receiver.name === "Gesture") return {
43912
+ if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Gesture") return {
44555
43913
  factoryName: methodName,
44556
43914
  chainMethodNames,
44557
43915
  numberOfTapsArgument
44558
43916
  };
44559
43917
  if (methodName === "numberOfTaps" && numberOfTapsArgument === null && callExpression.arguments?.length === 1) numberOfTapsArgument = callExpression.arguments[0] ?? null;
44560
43918
  chainMethodNames.push(methodName);
44561
- cursor = receiver;
43919
+ cursor = callee.object;
44562
43920
  }
44563
43921
  return null;
44564
43922
  };
@@ -48224,8 +47582,6 @@ const roleSupportsAriaProps = defineRule({
48224
47582
  const propName = propRawName.toLowerCase();
48225
47583
  if (!propName.startsWith("aria-")) continue;
48226
47584
  if (!ARIA_PROPERTIES.has(propName)) continue;
48227
- const attributeValue = attributeNode.value;
48228
- if (attributeValue && isNodeOfType(attributeValue, "JSXExpressionContainer") && isNullishExpression(attributeValue.expression)) continue;
48229
47585
  (ariaAttributes ??= []).push({
48230
47586
  attribute,
48231
47587
  propName
@@ -48600,6 +47956,11 @@ const isUseEffectEventSymbol = (symbol) => {
48600
47956
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
48601
47957
  return getHookNameFromCallee(initializer.callee) === "useEffectEvent";
48602
47958
  };
47959
+ const resolvesToLocalNonImportBinding = (identifier, scopes) => {
47960
+ const symbol = scopes.referenceFor(identifier)?.resolvedSymbol;
47961
+ return Boolean(symbol && symbol.kind !== "import");
47962
+ };
47963
+ const isNonReactEffectEventCallee = (callee, contextNode, scopes) => isNodeOfType(callee, "Identifier") && (isImportedFromNonReactModule(contextNode, callee.name) || resolvesToLocalNonImportBinding(callee, scopes));
48603
47964
  const isNonReactEffectEventSymbol = (symbol, contextNode, scopes) => {
48604
47965
  const initializer = symbol.initializer;
48605
47966
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
@@ -48943,8 +48304,7 @@ const serverAfterNonblocking = defineRule({
48943
48304
  if (!fileHasUseServerDirective && serverFunctionDepth === 0) return;
48944
48305
  if (!isNodeOfType(node.callee, "MemberExpression")) return;
48945
48306
  if (!isNodeOfType(node.callee.property, "Identifier")) return;
48946
- const receiver = stripParenExpression(node.callee.object);
48947
- const objectName = isNodeOfType(receiver, "Identifier") ? receiver.name : null;
48307
+ const objectName = isNodeOfType(node.callee.object, "Identifier") ? node.callee.object.name : null;
48948
48308
  if (!objectName) return;
48949
48309
  const methodName = node.callee.property.name;
48950
48310
  if (!isDeferrableSideEffectCall(objectName, methodName)) return;
@@ -49620,17 +48980,7 @@ const getMemberPropertyName$1 = (memberExpression) => {
49620
48980
  };
49621
48981
  const ascendMemberChain = (referenceIdentifier) => {
49622
48982
  let chainTip = referenceIdentifier;
49623
- while (chainTip.parent) {
49624
- if (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(chainTip.parent.type) && "expression" in chainTip.parent && chainTip.parent.expression === chainTip) {
49625
- chainTip = chainTip.parent;
49626
- continue;
49627
- }
49628
- if (isNodeOfType(chainTip.parent, "MemberExpression") && chainTip.parent.object === chainTip) {
49629
- chainTip = chainTip.parent;
49630
- continue;
49631
- }
49632
- break;
49633
- }
48983
+ while (chainTip.parent && isNodeOfType(chainTip.parent, "MemberExpression") && chainTip.parent.object === chainTip) chainTip = chainTip.parent;
49634
48984
  return chainTip;
49635
48985
  };
49636
48986
  const isDirectContentsMutation = (referenceIdentifier) => {
@@ -49655,8 +49005,7 @@ const isMutatedThroughCallArgument = (referenceIdentifier, scopes, mayFollowCall
49655
49005
  const callee = callExpression.callee;
49656
49006
  if (isNodeOfType(callee, "MemberExpression")) {
49657
49007
  const methodName = getMemberPropertyName$1(callee);
49658
- const calleeReceiver = stripParenExpression(callee.object);
49659
- return Boolean(isNodeOfType(calleeReceiver, "Identifier") && calleeReceiver.name === "Object" && methodName !== null && OBJECT_MUTATING_METHODS.has(methodName) && referenceArgumentIndex === 0);
49008
+ return Boolean(isNodeOfType(callee.object, "Identifier") && callee.object.name === "Object" && methodName !== null && OBJECT_MUTATING_METHODS.has(methodName) && referenceArgumentIndex === 0);
49660
49009
  }
49661
49010
  if (!mayFollowCalleeHop || !isNodeOfType(callee, "Identifier")) return false;
49662
49011
  const calleeSymbol = scopes.symbolFor(callee);
@@ -50386,7 +49735,7 @@ const walkServerFnChain = (outerNode) => {
50386
49735
  };
50387
49736
  if (!isNodeOfType(outerNode, "CallExpression")) return result;
50388
49737
  if (!isNodeOfType(outerNode.callee, "MemberExpression")) return result;
50389
- let currentNode = stripParenExpression(outerNode.callee.object);
49738
+ let currentNode = outerNode.callee.object;
50390
49739
  while (isNodeOfType(currentNode, "CallExpression")) {
50391
49740
  const calleeName = getCalleeName$2(currentNode);
50392
49741
  if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) {
@@ -50397,7 +49746,7 @@ const walkServerFnChain = (outerNode) => {
50397
49746
  }
50398
49747
  }
50399
49748
  if (calleeName && TANSTACK_INPUT_VALIDATOR_METHOD_NAMES.has(calleeName)) result.hasInputValidation = true;
50400
- if (isNodeOfType(currentNode.callee, "MemberExpression")) currentNode = stripParenExpression(currentNode.callee.object);
49749
+ if (isNodeOfType(currentNode.callee, "MemberExpression")) currentNode = currentNode.callee.object;
50401
49750
  else break;
50402
49751
  }
50403
49752
  return result;
@@ -51050,7 +50399,7 @@ const tanstackStartServerFnMethodOrder = defineRule({
51050
50399
  while (isNodeOfType(currentNode, "CallExpression") && isNodeOfType(currentNode.callee, "MemberExpression")) {
51051
50400
  const methodName = isNodeOfType(currentNode.callee.property, "Identifier") ? currentNode.callee.property.name : null;
51052
50401
  if (methodName) methodNames.unshift(methodName);
51053
- currentNode = stripParenExpression(currentNode.callee.object);
50402
+ currentNode = currentNode.callee.object;
51054
50403
  }
51055
50404
  if (isNodeOfType(currentNode, "CallExpression") && isNodeOfType(currentNode.callee, "Identifier")) {
51056
50405
  if (!TANSTACK_SERVER_FN_NAMES.has(currentNode.callee.name)) return;
@@ -54132,18 +53481,6 @@ const reactDoctorRules = [
54132
53481
  category: "Bugs"
54133
53482
  }
54134
53483
  },
54135
- {
54136
- key: "react-doctor/no-locale-format-in-render",
54137
- id: "no-locale-format-in-render",
54138
- source: "react-doctor",
54139
- originallyExternal: false,
54140
- rule: {
54141
- ...noLocaleFormatInRender,
54142
- framework: "global",
54143
- category: "Bugs",
54144
- requires: [...new Set(["react", ...noLocaleFormatInRender.requires ?? []])]
54145
- }
54146
- },
54147
53484
  {
54148
53485
  key: "react-doctor/no-long-transition-duration",
54149
53486
  id: "no-long-transition-duration",