oxlint-plugin-react-doctor 0.7.6-dev.4fb3694 → 0.7.6-dev.543ebfe

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +724 -108
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1180,8 +1180,8 @@ const stripParenExpression = (node) => {
1180
1180
  return current;
1181
1181
  };
1182
1182
  //#endregion
1183
- //#region src/plugin/utils/get-root-identifier-name.ts
1184
- const getRootIdentifierName = (node, options) => {
1183
+ //#region src/plugin/utils/get-root-identifier.ts
1184
+ const getRootIdentifier$1 = (node, options) => {
1185
1185
  if (!node) return null;
1186
1186
  const followCallChains = options?.followCallChains === true;
1187
1187
  let cursor = node;
@@ -1192,16 +1192,18 @@ const getRootIdentifierName = (node, options) => {
1192
1192
  continue;
1193
1193
  }
1194
1194
  if (followCallChains && isNodeOfType(cursor, "CallExpression")) {
1195
- const callee = cursor.callee;
1196
- if (!isNodeOfType(callee, "MemberExpression")) return null;
1197
- cursor = callee.object;
1195
+ if (!isNodeOfType(cursor.callee, "MemberExpression")) return null;
1196
+ cursor = cursor.callee.object;
1198
1197
  continue;
1199
1198
  }
1200
1199
  break;
1201
1200
  }
1202
- return isNodeOfType(cursor, "Identifier") ? cursor.name : null;
1201
+ return isNodeOfType(cursor, "Identifier") ? cursor : null;
1203
1202
  };
1204
1203
  //#endregion
1204
+ //#region src/plugin/utils/get-root-identifier-name.ts
1205
+ const getRootIdentifierName = (node, options) => getRootIdentifier$1(node, options)?.name ?? null;
1206
+ //#endregion
1205
1207
  //#region src/plugin/rules/state-and-effects/advanced-event-handler-refs.ts
1206
1208
  const STABLE_HANDLER_HOOK_NAMES = new Set([
1207
1209
  "useCallback",
@@ -5962,7 +5964,7 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
5962
5964
  //#endregion
5963
5965
  //#region src/plugin/utils/get-static-property-key-name.ts
5964
5966
  const getStaticPropertyKeyName = (node, options = {}) => {
5965
- if (!isNodeOfType(node, "Property")) return null;
5967
+ if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition")) return null;
5966
5968
  if (node.computed) {
5967
5969
  if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
5968
5970
  return null;
@@ -6021,7 +6023,7 @@ const isPureEventBlockerHandler = (attribute) => {
6021
6023
  //#endregion
6022
6024
  //#region src/plugin/utils/resolve-const-identifier-alias.ts
6023
6025
  const resolveConstIdentifierAlias = (identifier, scopes) => {
6024
- if (!isNodeOfType(identifier, "Identifier")) return null;
6026
+ if (!isNodeOfType(identifier, "Identifier") && !isNodeOfType(identifier, "JSXIdentifier")) return null;
6025
6027
  const visitedSymbolIds = /* @__PURE__ */ new Set();
6026
6028
  let symbol = scopes.symbolFor(identifier);
6027
6029
  while (symbol?.kind === "const") {
@@ -6296,6 +6298,131 @@ const isGlobalMethodCall = (node, objectName, methodName) => {
6296
6298
  return isNodeOfType(receiver, "Identifier") && receiver.name === objectName && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
6297
6299
  };
6298
6300
  //#endregion
6301
+ //#region src/plugin/utils/collect-safely-validated-local-storage-keys.ts
6302
+ const isLocalStorageCall = (node, methodName) => {
6303
+ if (!isNodeOfType(node, "CallExpression")) return false;
6304
+ const callee = stripParenExpression(node.callee);
6305
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
6306
+ const receiver = stripParenExpression(callee.object);
6307
+ return isNodeOfType(receiver, "Identifier") && receiver.name === "localStorage" && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
6308
+ };
6309
+ const catchReturnsFallback = (tryStatement) => {
6310
+ const handler = tryStatement.handler;
6311
+ if (!handler) return false;
6312
+ let hasReturn = false;
6313
+ let hasThrow = false;
6314
+ walkAst(handler.body, (child) => {
6315
+ if (isFunctionLike$1(child)) return false;
6316
+ if (isNodeOfType(child, "ReturnStatement")) hasReturn = true;
6317
+ if (isNodeOfType(child, "ThrowStatement")) hasThrow = true;
6318
+ });
6319
+ return hasReturn && !hasThrow;
6320
+ };
6321
+ const resolveValidatorFunction = (callee, scopes) => {
6322
+ const unwrappedCallee = stripParenExpression(callee);
6323
+ if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
6324
+ const symbol = scopes.symbolFor(unwrappedCallee);
6325
+ if (!symbol) return null;
6326
+ const candidate = symbol.kind === "function" ? symbol.declarationNode : symbol.initializer;
6327
+ return isFunctionLike$1(candidate) ? candidate : null;
6328
+ };
6329
+ const validatorChecksPayloadProperties = (validatorFunction, scopes) => {
6330
+ if (!isFunctionLike$1(validatorFunction)) return false;
6331
+ const firstParameter = validatorFunction.params?.[0];
6332
+ if (!firstParameter || !isNodeOfType(firstParameter, "Identifier")) return false;
6333
+ const parameterSymbol = scopes.symbolFor(firstParameter);
6334
+ if (!parameterSymbol) return false;
6335
+ const payloadSymbolIds = new Set([parameterSymbol.id]);
6336
+ let hasPropertyTypeCheck = false;
6337
+ walkAst(validatorFunction.body, (child) => {
6338
+ if (isFunctionLike$1(child)) return false;
6339
+ if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier") && child.init) {
6340
+ const initializer = stripParenExpression(child.init);
6341
+ if (isNodeOfType(initializer, "Identifier")) {
6342
+ const initializerSymbol = scopes.symbolFor(initializer);
6343
+ if (initializerSymbol && payloadSymbolIds.has(initializerSymbol.id)) {
6344
+ const aliasSymbol = scopes.symbolFor(child.id);
6345
+ if (aliasSymbol) payloadSymbolIds.add(aliasSymbol.id);
6346
+ }
6347
+ }
6348
+ }
6349
+ if (!isNodeOfType(child, "UnaryExpression") || child.operator !== "typeof") return;
6350
+ const checkedValue = stripParenExpression(child.argument);
6351
+ if (!isNodeOfType(checkedValue, "MemberExpression")) return;
6352
+ const receiver = stripParenExpression(checkedValue.object);
6353
+ if (!isNodeOfType(receiver, "Identifier")) return;
6354
+ const receiverSymbol = scopes.symbolFor(receiver);
6355
+ if (receiverSymbol && payloadSymbolIds.has(receiverSymbol.id)) hasPropertyTypeCheck = true;
6356
+ });
6357
+ return hasPropertyTypeCheck;
6358
+ };
6359
+ const incrementCount = (counts, keyValue) => {
6360
+ counts.set(keyValue, (counts.get(keyValue) ?? 0) + 1);
6361
+ };
6362
+ const isReturnedExpression = (expression) => {
6363
+ const parent = expression.parent;
6364
+ return Boolean(parent && isNodeOfType(parent, "ReturnStatement") && parent.argument === expression);
6365
+ };
6366
+ const collectSafelyValidatedLocalStorageKeys = ({ programNode, scopes, resolveKey }) => {
6367
+ const readCountsByKey = /* @__PURE__ */ new Map();
6368
+ const safeReadCountsByKey = /* @__PURE__ */ new Map();
6369
+ const safeReadSymbolIds = /* @__PURE__ */ new Set();
6370
+ walkAst(programNode, (child) => {
6371
+ if (!isNodeOfType(child, "CallExpression") || !isLocalStorageCall(child, "getItem")) return;
6372
+ const keyArgument = child.arguments?.[0];
6373
+ if (!keyArgument) return;
6374
+ const keyValue = resolveKey(keyArgument);
6375
+ if (keyValue !== null) incrementCount(readCountsByKey, keyValue);
6376
+ });
6377
+ walkAst(programNode, (child) => {
6378
+ if (!isNodeOfType(child, "TryStatement") || !catchReturnsFallback(child)) return;
6379
+ const rawValueKeys = /* @__PURE__ */ new Map();
6380
+ const parsedValueSources = /* @__PURE__ */ new Map();
6381
+ walkAst(child.block, (tryChild) => {
6382
+ if (isFunctionLike$1(tryChild)) return false;
6383
+ if (isNodeOfType(tryChild, "VariableDeclarator") && isNodeOfType(tryChild.id, "Identifier") && tryChild.init) {
6384
+ const initializer = stripParenExpression(tryChild.init);
6385
+ if (isNodeOfType(initializer, "CallExpression") && isLocalStorageCall(initializer, "getItem")) {
6386
+ const keyArgument = initializer.arguments?.[0];
6387
+ const bindingSymbol = scopes.symbolFor(tryChild.id);
6388
+ if (keyArgument && bindingSymbol) {
6389
+ const keyValue = resolveKey(keyArgument);
6390
+ if (keyValue !== null) rawValueKeys.set(bindingSymbol.id, keyValue);
6391
+ }
6392
+ }
6393
+ if (isNodeOfType(initializer, "CallExpression") && isGlobalMethodCall(initializer, "JSON", "parse")) {
6394
+ const rawValueArgument = initializer.arguments?.[0];
6395
+ const parsedValueSymbol = scopes.symbolFor(tryChild.id);
6396
+ if (rawValueArgument && isNodeOfType(rawValueArgument, "Identifier") && parsedValueSymbol) {
6397
+ const rawValueSymbol = scopes.symbolFor(rawValueArgument);
6398
+ if (rawValueSymbol && rawValueKeys.has(rawValueSymbol.id)) parsedValueSources.set(parsedValueSymbol.id, rawValueSymbol.id);
6399
+ }
6400
+ }
6401
+ }
6402
+ if (!isNodeOfType(tryChild, "ConditionalExpression") || !isReturnedExpression(tryChild)) return;
6403
+ const test = stripParenExpression(tryChild.test);
6404
+ if (!isNodeOfType(test, "CallExpression")) return;
6405
+ const testedValue = test.arguments?.[0];
6406
+ if (!testedValue || !isNodeOfType(testedValue, "Identifier")) return;
6407
+ const parsedValueSymbol = scopes.symbolFor(testedValue);
6408
+ if (!parsedValueSymbol) return;
6409
+ const rawValueSymbolId = parsedValueSources.get(parsedValueSymbol.id);
6410
+ if (rawValueSymbolId === void 0) return;
6411
+ const returnedValue = stripParenExpression(tryChild.consequent);
6412
+ if (!isNodeOfType(returnedValue, "Identifier") || scopes.symbolFor(returnedValue)?.id !== parsedValueSymbol.id) return;
6413
+ const validatorFunction = resolveValidatorFunction(test.callee, scopes);
6414
+ if (!validatorFunction || !validatorChecksPayloadProperties(validatorFunction, scopes)) return;
6415
+ const keyValue = rawValueKeys.get(rawValueSymbolId);
6416
+ if (keyValue === void 0 || safeReadSymbolIds.has(rawValueSymbolId)) return;
6417
+ safeReadSymbolIds.add(rawValueSymbolId);
6418
+ incrementCount(safeReadCountsByKey, keyValue);
6419
+ });
6420
+ });
6421
+ const safeKeys = /* @__PURE__ */ new Set();
6422
+ for (const [keyValue, readCount] of readCountsByKey) if (safeReadCountsByKey.get(keyValue) === readCount) safeKeys.add(keyValue);
6423
+ return safeKeys;
6424
+ };
6425
+ //#endregion
6299
6426
  //#region src/plugin/rules/client/client-localstorage-no-version.ts
6300
6427
  const VERSIONED_KEY_PATTERN = /(?:[._:-]v\d+|@\d+|\bv\d+\b)/i;
6301
6428
  const CAMEL_CASE_VERSIONED_KEY_PATTERN = /[a-z]V\d+/;
@@ -6317,26 +6444,39 @@ const clientLocalstorageNoVersion = defineRule({
6317
6444
  severity: "warn",
6318
6445
  category: "Correctness",
6319
6446
  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.",
6320
- create: (context) => ({ CallExpression(node) {
6321
- if (!isNodeOfType(node.callee, "MemberExpression")) return;
6322
- const receiver = stripParenExpression(node.callee.object);
6323
- if (!isNodeOfType(receiver, "Identifier")) return;
6324
- if (receiver.name !== "localStorage") return;
6325
- if (!isNodeOfType(node.callee.property, "Identifier")) return;
6326
- if (node.callee.property.name !== "setItem") return;
6327
- const keyArg = node.arguments?.[0];
6328
- if (!keyArg) return;
6329
- const keyValue = resolveStringKey(keyArg, context);
6330
- if (keyValue === null) return;
6331
- if (isVersionedKey(keyValue)) return;
6332
- const valueArg = node.arguments?.[1];
6333
- if (!valueArg) return;
6334
- if (!isJsonStringifyCall(valueArg)) return;
6335
- context.report({
6336
- node: keyArg,
6337
- 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").`
6338
- });
6339
- } })
6447
+ create: (context) => {
6448
+ let safelyValidatedStorageKeys = /* @__PURE__ */ new Set();
6449
+ return {
6450
+ Program(node) {
6451
+ safelyValidatedStorageKeys = collectSafelyValidatedLocalStorageKeys({
6452
+ programNode: node,
6453
+ scopes: context.scopes,
6454
+ resolveKey: (keyNode) => resolveStringKey(keyNode, context)
6455
+ });
6456
+ },
6457
+ CallExpression(node) {
6458
+ if (!isNodeOfType(node.callee, "MemberExpression")) return;
6459
+ const receiver = stripParenExpression(node.callee.object);
6460
+ if (!isNodeOfType(receiver, "Identifier")) return;
6461
+ if (receiver.name !== "localStorage") return;
6462
+ if (!isNodeOfType(node.callee.property, "Identifier")) return;
6463
+ if (node.callee.property.name !== "setItem") return;
6464
+ const keyArg = node.arguments?.[0];
6465
+ if (!keyArg) return;
6466
+ const keyValue = resolveStringKey(keyArg, context);
6467
+ if (keyValue === null) return;
6468
+ if (isVersionedKey(keyValue)) return;
6469
+ if (safelyValidatedStorageKeys.has(keyValue)) return;
6470
+ const valueArg = node.arguments?.[1];
6471
+ if (!valueArg) return;
6472
+ if (!isJsonStringifyCall(valueArg)) return;
6473
+ context.report({
6474
+ node: keyArg,
6475
+ 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").`
6476
+ });
6477
+ }
6478
+ };
6479
+ }
6340
6480
  });
6341
6481
  //#endregion
6342
6482
  //#region src/plugin/rules/client/client-passive-event-listeners.ts
@@ -15972,14 +16112,21 @@ const jsxFilenameExtension = defineRule({
15972
16112
  });
15973
16113
  //#endregion
15974
16114
  //#region src/plugin/utils/is-jsx-fragment-element.ts
15975
- const isJsxFragmentElement = (node) => {
16115
+ const isJsxFragmentElement = (node, scopes) => {
15976
16116
  if (!isNodeOfType(node, "JSXOpeningElement")) return false;
15977
16117
  const elementName = node.name;
15978
- if (isNodeOfType(elementName, "JSXIdentifier")) return elementName.name === "Fragment";
16118
+ if (isNodeOfType(elementName, "JSXIdentifier")) {
16119
+ if (!scopes) return elementName.name === "Fragment";
16120
+ const symbol = resolveConstIdentifierAlias(elementName, scopes);
16121
+ if (!symbol) return elementName.name === "Fragment" && scopes.isGlobalReference(elementName);
16122
+ return isImportedFromReact(symbol) && getImportedName(symbol.declarationNode) === "Fragment";
16123
+ }
15979
16124
  if (isNodeOfType(elementName, "JSXMemberExpression")) {
15980
16125
  if (!isNodeOfType(elementName.object, "JSXIdentifier")) return false;
15981
- if (elementName.object.name !== "React") return false;
15982
- return elementName.property.name === "Fragment";
16126
+ if (elementName.property.name !== "Fragment") return false;
16127
+ if (!scopes) return elementName.object.name === "React";
16128
+ if (isReactNamespaceImport(elementName.object, scopes)) return true;
16129
+ return elementName.object.name === "React" && scopes.isGlobalReference(elementName.object);
15983
16130
  }
15984
16131
  return false;
15985
16132
  };
@@ -16005,7 +16152,7 @@ const jsxFragments = defineRule({
16005
16152
  if (mode !== "syntax") return;
16006
16153
  if (!node.closingElement) return;
16007
16154
  const openingElement = node.openingElement;
16008
- if (!isJsxFragmentElement(openingElement)) return;
16155
+ if (!isJsxFragmentElement(openingElement, context.scopes)) return;
16009
16156
  if (openingElement.attributes.length > 0) return;
16010
16157
  context.report({
16011
16158
  node: openingElement,
@@ -19146,7 +19293,7 @@ const jsxNoUselessFragment = defineRule({
19146
19293
  return {
19147
19294
  JSXElement(node) {
19148
19295
  const openingElement = node.openingElement;
19149
- if (!isJsxFragmentElement(openingElement)) return;
19296
+ if (!isJsxFragmentElement(openingElement, context.scopes)) return;
19150
19297
  if (hasJsxKeyAttribute(openingElement)) return;
19151
19298
  if (checkChildren(node, openingElement, node.children)) return;
19152
19299
  if (isChildOfHtmlElement(node)) context.report({
@@ -20388,8 +20535,24 @@ const hasDirective = (programNode, directive) => {
20388
20535
  return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
20389
20536
  };
20390
20537
  //#endregion
20391
- //#region src/plugin/utils/is-component-assignment.ts
20392
- const isComponentAssignment = (node) => isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && isUppercaseName(node.id.name) && Boolean(node.init) && (isNodeOfType(node.init, "ArrowFunctionExpression") || isNodeOfType(node.init, "FunctionExpression"));
20538
+ //#region src/plugin/utils/unwrap-object-integrity-expression.ts
20539
+ const OBJECT_INTEGRITY_METHOD_NAMES = new Set([
20540
+ "freeze",
20541
+ "seal",
20542
+ "preventExtensions"
20543
+ ]);
20544
+ const OBJECT_FREEZE_OR_SEAL_METHOD_NAMES = new Set(["freeze", "seal"]);
20545
+ const unwrapObjectIntegrityExpression = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
20546
+ let expression = stripParenExpression(node);
20547
+ while (isNodeOfType(expression, "CallExpression")) {
20548
+ const callee = stripParenExpression(expression.callee);
20549
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || callee.object.name !== "Object" || !scopes.isGlobalReference(callee.object) || !isNodeOfType(callee.property, "Identifier") || !methodNames.has(callee.property.name)) break;
20550
+ const wrappedExpression = expression.arguments[0];
20551
+ if (!wrappedExpression || isNodeOfType(wrappedExpression, "SpreadElement")) break;
20552
+ expression = stripParenExpression(wrappedExpression);
20553
+ }
20554
+ return expression;
20555
+ };
20393
20556
  //#endregion
20394
20557
  //#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
20395
20558
  const nextjsAsyncClientComponent = defineRule({
@@ -20415,10 +20578,10 @@ const nextjsAsyncClientComponent = defineRule({
20415
20578
  },
20416
20579
  VariableDeclarator(node) {
20417
20580
  if (!fileHasUseClient) return;
20418
- if (!isComponentAssignment(node)) return;
20419
- if (!isInlineFunctionExpression(node.init)) return;
20420
- if (!node.init.async) return;
20421
20581
  if (!isNodeOfType(node.id, "Identifier")) return;
20582
+ if (!isUppercaseName(node.id.name) || !node.init) return;
20583
+ const componentFunction = unwrapObjectIntegrityExpression(node.init, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES);
20584
+ if (!isInlineFunctionExpression(componentFunction) || !componentFunction.async) return;
20422
20585
  context.report({
20423
20586
  node,
20424
20587
  message: `Async client component "${node.id.name}" fails to render because client components can't be async.`
@@ -26154,6 +26317,9 @@ const functionContainsReactRenderOutput = (functionNode, scopes) => {
26154
26317
  return hasRenderOutput;
26155
26318
  };
26156
26319
  //#endregion
26320
+ //#region src/plugin/utils/is-component-assignment.ts
26321
+ const isComponentAssignment = (node) => isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && isUppercaseName(node.id.name) && Boolean(node.init) && (isNodeOfType(node.init, "ArrowFunctionExpression") || isNodeOfType(node.init, "FunctionExpression"));
26322
+ //#endregion
26157
26323
  //#region src/plugin/utils/is-component-declaration.ts
26158
26324
  const isComponentDeclaration = (node) => isNodeOfType(node, "FunctionDeclaration") && node.id !== null && Boolean(node.id?.name) && isUppercaseName(node.id.name);
26159
26325
  //#endregion
@@ -31795,6 +31961,26 @@ const noImgLazyWithHighFetchpriority = defineRule({
31795
31961
  } })
31796
31962
  });
31797
31963
  //#endregion
31964
+ //#region src/plugin/utils/react-ref-origin.ts
31965
+ const resolveReactRefSymbol = (memberExpression, scopes) => {
31966
+ const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
31967
+ if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticPropertyName(memberExpression) !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
31968
+ const symbol = resolveConstIdentifierAlias(receiver, scopes);
31969
+ if (!symbol?.initializer) return null;
31970
+ const initializer = stripParenExpression(symbol.initializer);
31971
+ if (!isNodeOfType(initializer, "CallExpression")) return null;
31972
+ return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
31973
+ };
31974
+ const hasReactRefCurrentOrigin = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
31975
+ const expression = stripParenExpression(node);
31976
+ if (resolveReactRefSymbol(expression, scopes)) return true;
31977
+ if (!isNodeOfType(expression, "Identifier")) return false;
31978
+ const symbol = resolveConstIdentifierAlias(expression, scopes);
31979
+ if (!symbol?.initializer || visitedSymbolIds.has(symbol.id)) return false;
31980
+ visitedSymbolIds.add(symbol.id);
31981
+ return hasReactRefCurrentOrigin(symbol.initializer, scopes, visitedSymbolIds);
31982
+ };
31983
+ //#endregion
31798
31984
  //#region src/plugin/rules/state-and-effects/no-impure-state-updater.ts
31799
31985
  const TIMER_FUNCTION_NAMES = new Set([
31800
31986
  "cancelAnimationFrame",
@@ -31826,6 +32012,33 @@ const NOTIFICATION_METHOD_NAMES = new Set([
31826
32012
  "success",
31827
32013
  "warning"
31828
32014
  ]);
32015
+ const NOTIFICATION_MODULE_SOURCES = new Set([
32016
+ "@chakra-ui/react",
32017
+ "@heroui/react",
32018
+ "@mantine/notifications",
32019
+ "antd",
32020
+ "react-hot-toast",
32021
+ "react-toastify",
32022
+ "sonner"
32023
+ ]);
32024
+ const SYNCHRONOUS_ARRAY_METHOD_NAMES = new Set([
32025
+ "every",
32026
+ "filter",
32027
+ "find",
32028
+ "findIndex",
32029
+ "flatMap",
32030
+ "forEach",
32031
+ "map",
32032
+ "reduce",
32033
+ "reduceRight",
32034
+ "some",
32035
+ "sort"
32036
+ ]);
32037
+ const isNotificationModuleSource = (source) => {
32038
+ if (!source) return false;
32039
+ for (const moduleSource of NOTIFICATION_MODULE_SOURCES) if (source === moduleSource || source.startsWith(`${moduleSource}/`)) return true;
32040
+ return /(?:^|[/_.-])(?:notification|toast)s?(?:$|[/_.-])/i.test(source);
32041
+ };
31829
32042
  const getMemberCall = (node) => {
31830
32043
  if (!isNodeOfType(node, "CallExpression")) return null;
31831
32044
  if (!isNodeOfType(node.callee, "MemberExpression") || node.callee.computed) return null;
@@ -31837,27 +32050,37 @@ const getMemberCall = (node) => {
31837
32050
  };
31838
32051
  const isNotificationReceiver = (receiver, scopes) => {
31839
32052
  if (!isNodeOfType(receiver, "Identifier")) return false;
31840
- if (!NOTIFICATION_RECEIVER_NAMES.has(receiver.name)) return false;
32053
+ const importBinding = getImportBindingForName(receiver, receiver.name);
32054
+ if (importBinding) return isNotificationModuleSource(importBinding.source) && (importBinding.exportedName === "default" || importBinding.exportedName !== null && NOTIFICATION_RECEIVER_NAMES.has(importBinding.exportedName));
31841
32055
  const symbol = scopes.symbolFor(receiver);
31842
- if (symbol?.kind === "import") return true;
31843
32056
  if (!isNodeOfType(symbol?.initializer, "CallExpression")) return false;
31844
32057
  const callee = symbol.initializer.callee;
31845
- return isNodeOfType(callee, "Identifier") && /^use(?:Message|Notification|Toast)$/.test(callee.name);
32058
+ if (!isNodeOfType(callee, "Identifier")) return false;
32059
+ const hookImport = getImportBindingForName(callee, callee.name);
32060
+ return Boolean(hookImport && isNotificationModuleSource(hookImport.source) && hookImport.exportedName !== null && /^use(?:Message|Notification|Toast)$/.test(hookImport.exportedName));
32061
+ };
32062
+ const isKnownGlobalObject = (node, expectedName, scopes) => isNodeOfType(node, "Identifier") && node.name === expectedName && scopes.isGlobalReference(node);
32063
+ const isGlobalMember = (node, expectedPropertyName, scopes) => isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.property, "Identifier") && node.property.name === expectedPropertyName && (isKnownGlobalObject(node.object, "window", scopes) || isKnownGlobalObject(node.object, "globalThis", scopes));
32064
+ const isStorageReceiver = (node, scopes) => {
32065
+ if (isNodeOfType(node, "Identifier") && STORAGE_RECEIVER_NAMES.has(node.name) && scopes.isGlobalReference(node)) return true;
32066
+ for (const receiverName of STORAGE_RECEIVER_NAMES) if (isGlobalMember(node, receiverName, scopes)) return true;
32067
+ return false;
31846
32068
  };
31847
32069
  const getKnownImpureCall = (callExpression, scopes) => {
31848
32070
  if (isNodeOfType(callExpression.callee, "Identifier") && TIMER_FUNCTION_NAMES.has(callExpression.callee.name) && scopes.isGlobalReference(callExpression.callee)) return `${callExpression.callee.name}()`;
32071
+ if (isNodeOfType(callExpression.callee, "MemberExpression") && !callExpression.callee.computed && isNodeOfType(callExpression.callee.property, "Identifier") && TIMER_FUNCTION_NAMES.has(callExpression.callee.property.name) && (isKnownGlobalObject(callExpression.callee.object, "window", scopes) || isKnownGlobalObject(callExpression.callee.object, "globalThis", scopes))) return `${callExpression.callee.property.name}()`;
31849
32072
  const memberCall = getMemberCall(callExpression);
31850
32073
  if (!memberCall) return null;
31851
32074
  const { methodName, receiver } = memberCall;
31852
- if (STORAGE_MUTATION_METHOD_NAMES.has(methodName) && isNodeOfType(receiver, "Identifier") && STORAGE_RECEIVER_NAMES.has(receiver.name) && scopes.isGlobalReference(receiver)) return `${receiver.name}.${methodName}()`;
31853
- if (EXTERNAL_READ_METHOD_NAMES.has(methodName)) return `.${methodName}()`;
31854
- if (NOTIFICATION_METHOD_NAMES.has(methodName) && isNodeOfType(receiver, "Identifier") && isNotificationReceiver(receiver, scopes)) return `${receiver.name}.${methodName}()`;
32075
+ if (STORAGE_MUTATION_METHOD_NAMES.has(methodName) && isStorageReceiver(receiver, scopes)) return `${methodName}()`;
32076
+ if (EXTERNAL_READ_METHOD_NAMES.has(methodName) && hasReactRefCurrentOrigin(receiver, scopes)) return `.${methodName}()`;
32077
+ const receiverRoot = getRootIdentifier$1(receiver);
32078
+ if (EXTERNAL_READ_METHOD_NAMES.has(methodName) && receiverRoot !== null && isKnownGlobalObject(receiverRoot, "document", scopes)) return `.${methodName}()`;
32079
+ if (NOTIFICATION_METHOD_NAMES.has(methodName) && isNotificationReceiver(receiver, scopes)) return `${methodName}()`;
31855
32080
  return null;
31856
32081
  };
31857
32082
  const getExternalAssignmentDescription = (assignmentTarget, updater, scopes) => {
31858
- let rootIdentifier = null;
31859
- if (isNodeOfType(assignmentTarget, "Identifier")) rootIdentifier = assignmentTarget;
31860
- else if (isNodeOfType(assignmentTarget, "MemberExpression") && isNodeOfType(assignmentTarget.object, "Identifier")) rootIdentifier = assignmentTarget.object;
32083
+ const rootIdentifier = getRootIdentifier$1(assignmentTarget);
31861
32084
  if (!rootIdentifier) return null;
31862
32085
  const updaterScope = scopes.ownScopeFor(updater);
31863
32086
  if (!updaterScope) return null;
@@ -31866,12 +32089,26 @@ const getExternalAssignmentDescription = (assignmentTarget, updater, scopes) =>
31866
32089
  if (symbol.kind === "parameter" && symbol.scope === updaterScope) return `the updater argument "${rootIdentifier.name}"`;
31867
32090
  return isDescendantScope(symbol.scope, updaterScope) ? null : `the captured value "${rootIdentifier.name}"`;
31868
32091
  };
32092
+ const isArrayValue = (node, scopes) => {
32093
+ const expression = stripParenExpression(node);
32094
+ if (isNodeOfType(expression, "ArrayExpression")) return true;
32095
+ if (!isNodeOfType(expression, "Identifier")) return false;
32096
+ return isNodeOfType(resolveConstIdentifierAlias(expression, scopes)?.initializer, "ArrayExpression");
32097
+ };
32098
+ const isDefinitelySynchronousCallback = (callback, scopes) => {
32099
+ const parent = callback.parent;
32100
+ if (!parent) return false;
32101
+ if (isNodeOfType(parent, "CallExpression") && parent.callee === callback) return true;
32102
+ if (!isNodeOfType(parent, "CallExpression") || !parent.arguments?.some((argument) => argument === callback)) return false;
32103
+ if (isNodeOfType(parent.callee, "MemberExpression") && !parent.callee.computed && isNodeOfType(parent.callee.property, "Identifier") && SYNCHRONOUS_ARRAY_METHOD_NAMES.has(parent.callee.property.name) && isArrayValue(parent.callee.object, scopes)) return true;
32104
+ return isNodeOfType(parent.callee, "MemberExpression") && !parent.callee.computed && isNodeOfType(parent.callee.object, "Identifier") && parent.callee.object.name === "Array" && scopes.isGlobalReference(parent.callee.object) && isNodeOfType(parent.callee.property, "Identifier") && parent.callee.property.name === "from" && parent.arguments[1] === callback;
32105
+ };
31869
32106
  const findImpureUpdaterOperation = (updater, scopes) => {
31870
32107
  const analysis = getProgramAnalysis(updater);
31871
32108
  let operation = null;
31872
32109
  walkAst(updater, (child) => {
31873
32110
  if (operation) return false;
31874
- if (child !== updater && isFunctionLike$1(child) && !executesDuringRender(child, scopes)) return false;
32111
+ if (child !== updater && isFunctionLike$1(child) && !isDefinitelySynchronousCallback(child, scopes)) return false;
31875
32112
  if (isNodeOfType(child, "CallExpression")) {
31876
32113
  if (isNodeOfType(child.callee, "Identifier") && analysis) {
31877
32114
  const calleeReference = getRef(analysis, child.callee);
@@ -31903,18 +32140,25 @@ const noImpureStateUpdater = defineRule({
31903
32140
  severity: "error",
31904
32141
  recommendation: "Keep state updater callbacks pure and return only the next state. Move notifications, storage, timers, ref writes, and other external work into the event or effect that queues the update.",
31905
32142
  create: (context) => ({ CallExpression(node) {
31906
- const updater = node.arguments?.[0];
31907
- if (!updater || !isFunctionLike$1(updater)) return;
32143
+ const updaterArgument = node.arguments?.[0];
32144
+ if (!updaterArgument) return;
31908
32145
  const analysis = getProgramAnalysis(node);
31909
32146
  if (!analysis || !isNodeOfType(node.callee, "Identifier")) return;
31910
32147
  const calleeReference = getRef(analysis, node.callee);
31911
32148
  if (!calleeReference || !isStateSetterCall(analysis, calleeReference)) return;
31912
32149
  const stateDeclarator = getUseStateDecl(analysis, calleeReference);
31913
32150
  if (!isNodeOfType(stateDeclarator, "VariableDeclarator") || !isNodeOfType(stateDeclarator.init, "CallExpression") || !isReactApiCall(stateDeclarator.init, "useState", context.scopes, { allowGlobalReactNamespace: true })) return;
32151
+ let updater = null;
32152
+ if (isFunctionLike$1(updaterArgument)) updater = updaterArgument;
32153
+ else if (isNodeOfType(updaterArgument, "Identifier")) {
32154
+ const updaterReference = getRef(analysis, updaterArgument);
32155
+ if (updaterReference) updater = resolveToFunction(updaterReference);
32156
+ }
32157
+ if (!updater) return;
31914
32158
  const operation = findImpureUpdaterOperation(updater, context.scopes);
31915
32159
  if (!operation) return;
31916
32160
  context.report({
31917
- node: updater,
32161
+ node: updaterArgument,
31918
32162
  message: `This state updater performs ${operation}. React may run updater functions more than once, so side effects here can repeat or observe inconsistent external state.`
31919
32163
  });
31920
32164
  } })
@@ -32203,11 +32447,12 @@ const isMemoCall = (node) => {
32203
32447
  };
32204
32448
  const isDefaultEquivalentComparator = (comparator) => isNodeOfType(comparator, "Identifier") && (comparator.name === "undefined" || comparator.name === "shallowEqual");
32205
32449
  const hasCustomComparator = (node) => isNodeOfType(node, "CallExpression") && (node.arguments?.length ?? 0) >= 2 && !isDefaultEquivalentComparator(node.arguments?.[1]);
32206
- const isInlineReference = (node) => {
32207
- if (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "bind") return "functions";
32208
- if (isNodeOfType(node, "ObjectExpression")) return "objects";
32209
- if (isNodeOfType(node, "ArrayExpression")) return "Arrays";
32210
- if (isNodeOfType(node, "JSXElement") || isNodeOfType(node, "JSXFragment")) return "JSX";
32450
+ const isInlineReference = (node, scopes) => {
32451
+ const referenceNode = unwrapObjectIntegrityExpression(node, scopes);
32452
+ if (isNodeOfType(referenceNode, "ArrowFunctionExpression") || isNodeOfType(referenceNode, "FunctionExpression") || isNodeOfType(referenceNode, "CallExpression") && isNodeOfType(referenceNode.callee, "MemberExpression") && isNodeOfType(referenceNode.callee.property, "Identifier") && referenceNode.callee.property.name === "bind") return "functions";
32453
+ if (isNodeOfType(referenceNode, "ObjectExpression")) return "objects";
32454
+ if (isNodeOfType(referenceNode, "ArrayExpression")) return "Arrays";
32455
+ if (isNodeOfType(referenceNode, "JSXElement") || isNodeOfType(referenceNode, "JSXFragment")) return "JSX";
32211
32456
  return null;
32212
32457
  };
32213
32458
  const noInlinePropOnMemoComponent = defineRule({
@@ -32237,7 +32482,7 @@ const noInlinePropOnMemoComponent = defineRule({
32237
32482
  let elementName = null;
32238
32483
  if (isNodeOfType(openingElement.name, "JSXIdentifier")) elementName = openingElement.name.name;
32239
32484
  if (!elementName || !memoizedComponentNames.has(elementName)) return;
32240
- const propType = isInlineReference(node.value.expression);
32485
+ const propType = isInlineReference(node.value.expression, context.scopes);
32241
32486
  if (propType) context.report({
32242
32487
  node: node.value.expression,
32243
32488
  message: `This redraws ${elementName} on every render because the prop is ${propType} built right here, so memo() can't skip it. Move it to a stable value with useMemo, useCallback, or module scope`
@@ -32758,6 +33003,18 @@ const INTL_FORMAT_METHOD_NAMES = new Set([
32758
33003
  "formatToParts",
32759
33004
  "formatRange"
32760
33005
  ]);
33006
+ const ABSENT_PROPERTY_PROOF = { status: "absent" };
33007
+ const PRESENT_PROPERTY_PROOF = { status: "present" };
33008
+ const UNDEFINED_PROPERTY_PROOF = { status: "undefined" };
33009
+ const UNKNOWN_PROPERTY_PROOF = { status: "unknown" };
33010
+ const READ_ONLY_OBJECT_METHOD_NAMES = new Set([
33011
+ "hasOwnProperty",
33012
+ "isPrototypeOf",
33013
+ "propertyIsEnumerable",
33014
+ "toLocaleString",
33015
+ "toString",
33016
+ "valueOf"
33017
+ ]);
32761
33018
  const isProvableDateExpression = (expression) => {
32762
33019
  if (!expression) return false;
32763
33020
  const unwrapped = stripParenExpression(expression);
@@ -32772,32 +33029,290 @@ const receiverNameLooksDateFlavored = (expression) => {
32772
33029
  if (isNodeOfType(unwrapped, "CallExpression")) return receiverNameLooksDateFlavored(unwrapped.callee);
32773
33030
  return false;
32774
33031
  };
32775
- const objectLiteralHasProperty = (objectExpression, propertyName) => {
32776
- if (!objectExpression) return false;
32777
- const unwrapped = stripParenExpression(objectExpression);
32778
- if (!isNodeOfType(unwrapped, "ObjectExpression")) return false;
32779
- for (const property of unwrapped.properties ?? []) {
32780
- if (!isNodeOfType(property, "Property")) continue;
32781
- if (property.computed) continue;
32782
- if (isNodeOfType(property.key, "Identifier") && property.key.name === propertyName) return true;
32783
- if (isNodeOfType(property.key, "Literal") && property.key.value === propertyName) return true;
33032
+ const isStaticUndefined = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
33033
+ const expression = stripParenExpression(node);
33034
+ if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return true;
33035
+ if (!isNodeOfType(expression, "Identifier")) return false;
33036
+ if (expression.name === "undefined" && scopes.isGlobalReference(expression)) return true;
33037
+ const symbol = scopes.symbolFor(expression);
33038
+ if (symbol?.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || visitedSymbolIds.has(symbol.id)) return false;
33039
+ visitedSymbolIds.add(symbol.id);
33040
+ return isStaticUndefined(symbol.initializer, scopes, visitedSymbolIds);
33041
+ };
33042
+ const isNestedAssignmentTarget = (expression) => {
33043
+ let target = expression;
33044
+ let parent = target.parent;
33045
+ while (parent) {
33046
+ if (isNodeOfType(parent, "AssignmentExpression")) return parent.left === target;
33047
+ if (isNodeOfType(parent, "ForInStatement") || isNodeOfType(parent, "ForOfStatement")) return parent.left === target;
33048
+ if (isNodeOfType(parent, "AssignmentPattern")) {
33049
+ if (parent.left !== target) return false;
33050
+ } else if (isNodeOfType(parent, "RestElement")) {
33051
+ if (parent.argument !== target) return false;
33052
+ } else if (isNodeOfType(parent, "ArrayPattern")) {
33053
+ if (!parent.elements?.some((element) => element === target)) return false;
33054
+ } else if (isNodeOfType(parent, "Property")) {
33055
+ if (parent.value !== target || !isNodeOfType(parent.parent, "ObjectPattern")) return false;
33056
+ } else if (isNodeOfType(parent, "ObjectPattern")) {
33057
+ if (!parent.properties?.some((property) => property === target)) return false;
33058
+ } else return false;
33059
+ target = parent;
33060
+ parent = target.parent;
33061
+ }
33062
+ return false;
33063
+ };
33064
+ const isPotentialMutationReference = (identifier, readNode) => {
33065
+ let expression = identifier;
33066
+ let parent = expression.parent;
33067
+ while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === expression) {
33068
+ expression = parent;
33069
+ parent = expression.parent;
33070
+ }
33071
+ const rootExpression = expression;
33072
+ let memberDepth = 0;
33073
+ while (parent && isNodeOfType(parent, "MemberExpression") && parent.object === expression) {
33074
+ memberDepth += 1;
33075
+ expression = parent;
33076
+ parent = expression.parent;
33077
+ while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === expression) {
33078
+ expression = parent;
33079
+ parent = expression.parent;
33080
+ }
33081
+ }
33082
+ if (!parent || isAstDescendant(identifier, readNode)) return false;
33083
+ if (isNestedAssignmentTarget(expression)) return true;
33084
+ if (isNodeOfType(parent, "AssignmentExpression") && parent.left === expression) return true;
33085
+ if (isNodeOfType(parent, "UpdateExpression") && parent.argument === expression) return true;
33086
+ if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expression) return true;
33087
+ if (isNodeOfType(parent, "CallExpression")) {
33088
+ if (parent.callee === expression) {
33089
+ if (memberDepth === 0) return true;
33090
+ const memberExpression = stripParenExpression(expression);
33091
+ return memberDepth === 1 && isNodeOfType(memberExpression, "MemberExpression") && !READ_ONLY_OBJECT_METHOD_NAMES.has(getStaticPropertyName(memberExpression) ?? "");
33092
+ }
33093
+ return memberDepth === 0 && parent.arguments?.some((argument) => argument === rootExpression);
32784
33094
  }
33095
+ if (isNodeOfType(parent, "NewExpression")) return memberDepth === 0 && parent.arguments?.some((argument) => argument === rootExpression);
33096
+ if (isNodeOfType(parent, "AssignmentExpression") && parent.right === rootExpression) return true;
32785
33097
  return false;
32786
33098
  };
32787
- const hasExplicitLocaleArgument = (argument) => {
33099
+ const getSimpleAlias = (identifier, scopes) => {
33100
+ let expression = identifier;
33101
+ let parent = expression.parent;
33102
+ while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === expression) {
33103
+ expression = parent;
33104
+ parent = expression.parent;
33105
+ }
33106
+ if (!isNodeOfType(parent, "VariableDeclarator") || parent.init !== expression || !isNodeOfType(parent.id, "Identifier")) return null;
33107
+ const symbol = scopes.symbolFor(parent.id);
33108
+ return symbol ? {
33109
+ symbol,
33110
+ readNode: parent.id
33111
+ } : null;
33112
+ };
33113
+ const getDirectCallForExpression = (expression) => {
33114
+ let callee = expression;
33115
+ let parent = callee.parent;
33116
+ while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === callee) {
33117
+ callee = parent;
33118
+ parent = callee.parent;
33119
+ }
33120
+ return isNodeOfType(parent, "CallExpression") && parent.callee === callee ? parent : null;
33121
+ };
33122
+ const getMethodOwner = (functionNode, scopes) => {
33123
+ const methodNode = functionNode.parent;
33124
+ if (isNodeOfType(methodNode, "Property") && methodNode.value === functionNode && isNodeOfType(methodNode.parent, "ObjectExpression")) {
33125
+ const objectParent = methodNode.parent.parent;
33126
+ if (!isNodeOfType(objectParent, "VariableDeclarator") || objectParent.init !== methodNode.parent || !isNodeOfType(objectParent.id, "Identifier")) return null;
33127
+ const ownerSymbol = scopes.symbolFor(objectParent.id);
33128
+ const methodName = getStaticPropertyKeyName(methodNode, { allowComputedString: true });
33129
+ return ownerSymbol && methodName ? {
33130
+ methodName,
33131
+ ownerKind: "object",
33132
+ ownerSymbol,
33133
+ isStatic: false
33134
+ } : null;
33135
+ }
33136
+ if (!isNodeOfType(methodNode, "MethodDefinition") || methodNode.value !== functionNode || !isNodeOfType(methodNode.parent, "ClassBody")) return null;
33137
+ const classNode = methodNode.parent.parent;
33138
+ if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression")) return null;
33139
+ let bindingIdentifier = isNodeOfType(classNode.id, "Identifier") ? classNode.id : null;
33140
+ if (!bindingIdentifier && isNodeOfType(classNode.parent, "VariableDeclarator")) bindingIdentifier = isNodeOfType(classNode.parent.id, "Identifier") ? classNode.parent.id : null;
33141
+ if (!bindingIdentifier) return null;
33142
+ const ownerSymbol = scopes.symbolFor(bindingIdentifier);
33143
+ const methodName = getStaticPropertyKeyName(methodNode, { allowComputedString: true });
33144
+ return ownerSymbol && methodName ? {
33145
+ methodName,
33146
+ ownerKind: "class",
33147
+ ownerSymbol,
33148
+ isStatic: Boolean(methodNode.static)
33149
+ } : null;
33150
+ };
33151
+ const doesSymbolResolveToOwner = (symbol, ownerSymbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
33152
+ if (!symbol) return false;
33153
+ if (symbol.declarationNode === ownerSymbol.declarationNode) return true;
33154
+ if (symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id)) return false;
33155
+ visitedSymbolIds.add(symbol.id);
33156
+ const initializer = stripParenExpression(symbol.initializer);
33157
+ return isNodeOfType(initializer, "Identifier") && doesSymbolResolveToOwner(scopes.symbolFor(initializer), ownerSymbol, scopes, visitedSymbolIds);
33158
+ };
33159
+ const isClassInstanceExpression = (expression, ownerSymbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
33160
+ const candidate = stripParenExpression(expression);
33161
+ if (isNodeOfType(candidate, "NewExpression") && isNodeOfType(candidate.callee, "Identifier")) return doesSymbolResolveToOwner(scopes.symbolFor(candidate.callee), ownerSymbol, scopes);
33162
+ if (!isNodeOfType(candidate, "Identifier")) return false;
33163
+ const symbol = scopes.symbolFor(candidate);
33164
+ if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id)) return false;
33165
+ visitedSymbolIds.add(symbol.id);
33166
+ return isClassInstanceExpression(symbol.initializer, ownerSymbol, scopes, visitedSymbolIds);
33167
+ };
33168
+ const getMethodCalls = (functionNode, scopes) => {
33169
+ const owner = getMethodOwner(functionNode, scopes);
33170
+ if (!owner) return [];
33171
+ const calls = [];
33172
+ walkAst(scopes.rootScope.node, (child) => {
33173
+ if (!isNodeOfType(child, "MemberExpression")) return;
33174
+ if (getStaticPropertyName(child) !== owner.methodName) return;
33175
+ const receiver = stripParenExpression(child.object);
33176
+ let doesReceiverMatch = isNodeOfType(receiver, "Identifier") && doesSymbolResolveToOwner(scopes.symbolFor(receiver), owner.ownerSymbol, scopes);
33177
+ if (owner.ownerKind === "class" && !owner.isStatic) doesReceiverMatch = isClassInstanceExpression(receiver, owner.ownerSymbol, scopes);
33178
+ if (!doesReceiverMatch) return;
33179
+ const call = getDirectCallForExpression(child);
33180
+ if (call) calls.push(call);
33181
+ });
33182
+ return calls;
33183
+ };
33184
+ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, scopes, visitedSymbolIds, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
33185
+ if (visitedFunctionNodes.has(functionNode)) return false;
33186
+ visitedFunctionNodes.add(functionNode);
33187
+ const usageFunction = findEnclosingFunction(usageNode);
33188
+ const immediateCall = getDirectCallForExpression(functionNode);
33189
+ if (immediateCall) {
33190
+ const immediateCallFunction = findEnclosingFunction(immediateCall);
33191
+ if (immediateCallFunction === usageFunction) {
33192
+ const immediateCallStart = getRangeStart(immediateCall);
33193
+ return immediateCallStart === null || immediateCallStart < usageBoundary;
33194
+ }
33195
+ if (!immediateCallFunction) return usageFunction !== null;
33196
+ return isFunctionInvokedBeforeUsage(immediateCallFunction, usageNode, usageBoundary, scopes, visitedSymbolIds, new Set(visitedFunctionNodes));
33197
+ }
33198
+ for (const methodCall of getMethodCalls(functionNode, scopes)) {
33199
+ const methodCallFunction = findEnclosingFunction(methodCall);
33200
+ if (methodCallFunction === usageFunction) {
33201
+ const methodCallStart = getRangeStart(methodCall);
33202
+ if (methodCallStart === null || methodCallStart < usageBoundary) return true;
33203
+ continue;
33204
+ }
33205
+ if (!methodCallFunction) {
33206
+ if (usageFunction) return true;
33207
+ continue;
33208
+ }
33209
+ if (isFunctionInvokedBeforeUsage(methodCallFunction, usageNode, usageBoundary, scopes, new Set(visitedSymbolIds), new Set(visitedFunctionNodes))) return true;
33210
+ }
33211
+ const bindingIdentifier = getFunctionBindingIdentifier(functionNode);
33212
+ if (!bindingIdentifier) return false;
33213
+ const symbol = scopes.symbolFor(bindingIdentifier);
33214
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
33215
+ visitedSymbolIds.add(symbol.id);
33216
+ let wasInvokedBeforeUsage = false;
33217
+ walkAst(scopes.rootScope.node, (child) => {
33218
+ if (wasInvokedBeforeUsage || !isNodeOfType(child, "Identifier")) return;
33219
+ if (scopes.symbolFor(child)?.declarationNode !== symbol.declarationNode) return;
33220
+ const call = getDirectCallForExpression(child);
33221
+ if (!call) return false;
33222
+ const callFunction = findEnclosingFunction(call);
33223
+ if (callFunction === usageFunction) {
33224
+ const callStart = getRangeStart(call);
33225
+ wasInvokedBeforeUsage = callStart === null || callStart < usageBoundary;
33226
+ return;
33227
+ }
33228
+ if (!callFunction) {
33229
+ wasInvokedBeforeUsage = usageFunction !== null;
33230
+ return;
33231
+ }
33232
+ wasInvokedBeforeUsage = isFunctionInvokedBeforeUsage(callFunction, usageNode, usageBoundary, scopes, new Set(visitedSymbolIds), new Set(visitedFunctionNodes));
33233
+ });
33234
+ return wasInvokedBeforeUsage;
33235
+ };
33236
+ const getMutationUsageBoundary = (usageNode, readNode) => {
33237
+ const readStart = getRangeStart(readNode);
33238
+ let readExpression = readNode;
33239
+ let readParent = readExpression.parent;
33240
+ while (readParent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(readParent.type) && "expression" in readParent && readParent.expression === readExpression) {
33241
+ readExpression = readParent;
33242
+ readParent = readExpression.parent;
33243
+ }
33244
+ if ((isNodeOfType(usageNode, "CallExpression") || isNodeOfType(usageNode, "NewExpression")) && usageNode.arguments?.some((argument) => argument === readExpression)) return usageNode.range?.[1] ?? null;
33245
+ if (isAstDescendant(readNode, usageNode)) return readStart;
33246
+ return getRangeStart(usageNode);
33247
+ };
33248
+ const wasMutatedBeforeUsage = (symbol, usageNode, readNode, scopes, visitedMutationSymbolIds = /* @__PURE__ */ new Set(), inheritedUsageBoundary, readNodesBySymbolId = /* @__PURE__ */ new Map()) => {
33249
+ if (visitedMutationSymbolIds.has(symbol.id)) return false;
33250
+ visitedMutationSymbolIds.add(symbol.id);
33251
+ const usageBoundary = inheritedUsageBoundary === void 0 ? getMutationUsageBoundary(usageNode, readNode) : inheritedUsageBoundary;
33252
+ if (typeof usageBoundary !== "number") return true;
33253
+ const usageFunction = findEnclosingFunction(usageNode);
33254
+ return symbol.references.some((reference) => {
33255
+ const referenceStart = getRangeStart(reference.identifier);
33256
+ const simpleAlias = getSimpleAlias(reference.identifier, scopes);
33257
+ if (simpleAlias) return wasMutatedBeforeUsage(simpleAlias.symbol, usageNode, readNodesBySymbolId.get(simpleAlias.symbol.id) ?? simpleAlias.readNode, scopes, new Set(visitedMutationSymbolIds), usageBoundary, readNodesBySymbolId);
33258
+ if (!isPotentialMutationReference(reference.identifier, readNode)) return false;
33259
+ if (referenceStart === null) return true;
33260
+ const mutationFunction = findEnclosingFunction(reference.identifier);
33261
+ if (mutationFunction === usageFunction) return referenceStart < usageBoundary;
33262
+ if (!mutationFunction) return usageFunction !== null;
33263
+ if (usageFunction && isAstDescendant(usageFunction, mutationFunction)) return true;
33264
+ return isFunctionInvokedBeforeUsage(mutationFunction, usageNode, usageBoundary, scopes, /* @__PURE__ */ new Set());
33265
+ });
33266
+ };
33267
+ const getObjectPropertyProof = (objectExpression, propertyName, scopes, usageNode, visitedSymbolIds = /* @__PURE__ */ new Set(), inheritedUsageBoundary, readNodesBySymbolId = /* @__PURE__ */ new Map()) => {
33268
+ if (!objectExpression) return ABSENT_PROPERTY_PROOF;
33269
+ const unwrapped = stripParenExpression(objectExpression);
33270
+ if (isNodeOfType(unwrapped, "Literal") || isStaticUndefined(unwrapped, scopes)) return ABSENT_PROPERTY_PROOF;
33271
+ if (isNodeOfType(unwrapped, "Identifier")) {
33272
+ const symbol = scopes.symbolFor(unwrapped);
33273
+ const nextReadNodesBySymbolId = new Map(readNodesBySymbolId);
33274
+ if (symbol) nextReadNodesBySymbolId.set(symbol.id, unwrapped);
33275
+ const usageBoundary = inheritedUsageBoundary === void 0 ? getMutationUsageBoundary(usageNode, unwrapped) : inheritedUsageBoundary;
33276
+ if (symbol?.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || visitedSymbolIds.has(symbol.id) || wasMutatedBeforeUsage(symbol, usageNode, unwrapped, scopes, /* @__PURE__ */ new Set(), usageBoundary, nextReadNodesBySymbolId)) return UNKNOWN_PROPERTY_PROOF;
33277
+ visitedSymbolIds.add(symbol.id);
33278
+ return getObjectPropertyProof(symbol.initializer, propertyName, scopes, usageNode, visitedSymbolIds, usageBoundary, nextReadNodesBySymbolId);
33279
+ }
33280
+ if (isNodeOfType(unwrapped, "ConditionalExpression")) {
33281
+ const consequent = getObjectPropertyProof(unwrapped.consequent, propertyName, scopes, usageNode, new Set(visitedSymbolIds), inheritedUsageBoundary, new Map(readNodesBySymbolId));
33282
+ const alternate = getObjectPropertyProof(unwrapped.alternate, propertyName, scopes, usageNode, new Set(visitedSymbolIds), inheritedUsageBoundary, new Map(readNodesBySymbolId));
33283
+ return consequent.status === alternate.status ? consequent : UNKNOWN_PROPERTY_PROOF;
33284
+ }
33285
+ if (isNodeOfType(unwrapped, "CallExpression") && isNodeOfType(unwrapped.callee, "MemberExpression") && !unwrapped.callee.computed && isNodeOfType(unwrapped.callee.object, "Identifier") && unwrapped.callee.object.name === "Object" && scopes.isGlobalReference(unwrapped.callee.object) && isNodeOfType(unwrapped.callee.property, "Identifier") && unwrapped.callee.property.name === "freeze") return getObjectPropertyProof(unwrapped.arguments?.[0], propertyName, scopes, usageNode, visitedSymbolIds, inheritedUsageBoundary, readNodesBySymbolId);
33286
+ if (!isNodeOfType(unwrapped, "ObjectExpression")) return UNKNOWN_PROPERTY_PROOF;
33287
+ const properties = unwrapped.properties ?? [];
33288
+ for (let propertyIndex = properties.length - 1; propertyIndex >= 0; propertyIndex -= 1) {
33289
+ const property = properties[propertyIndex];
33290
+ if (!property) continue;
33291
+ if (isNodeOfType(property, "SpreadElement")) {
33292
+ const spreadProof = getObjectPropertyProof(property.argument, propertyName, scopes, unwrapped, new Set(visitedSymbolIds), void 0);
33293
+ if (spreadProof.status !== "absent") return spreadProof;
33294
+ continue;
33295
+ }
33296
+ if (!isNodeOfType(property, "Property") || getStaticPropertyKeyName(property) !== propertyName) continue;
33297
+ return isStaticUndefined(property.value, scopes) ? UNDEFINED_PROPERTY_PROOF : PRESENT_PROPERTY_PROOF;
33298
+ }
33299
+ return ABSENT_PROPERTY_PROOF;
33300
+ };
33301
+ const objectHasExplicitProperty = (objectExpression, propertyName, scopes, usageNode) => getObjectPropertyProof(objectExpression, propertyName, scopes, usageNode).status === "present";
33302
+ const hasExplicitLocaleArgument = (argument, scopes) => {
32788
33303
  if (!argument) return false;
32789
33304
  const unwrapped = stripParenExpression(argument);
32790
- if (isNodeOfType(unwrapped, "Identifier") && unwrapped.name === "undefined") return false;
33305
+ if (isNodeOfType(unwrapped, "Identifier") && unwrapped.name === "undefined" && scopes.isGlobalReference(unwrapped) || isNodeOfType(unwrapped, "UnaryExpression") && unwrapped.operator === "void") return false;
32791
33306
  return true;
32792
33307
  };
32793
- const isDeterministicLocaleMethodCall = (call, methodName, receiverIsProvablyDate) => {
33308
+ const isDeterministicLocaleMethodCall = (call, methodName, receiverIsProvablyDate, scopes) => {
32794
33309
  const localeArgument = call.arguments?.[0];
32795
- if (!hasExplicitLocaleArgument(localeArgument)) return false;
33310
+ if (!hasExplicitLocaleArgument(localeArgument, scopes)) return false;
32796
33311
  const optionsArgument = call.arguments?.[1];
32797
- if (objectLiteralHasProperty(optionsArgument, "timeZone")) return true;
33312
+ if (objectHasExplicitProperty(optionsArgument, "timeZone", scopes, call)) return true;
32798
33313
  return !DATE_ONLY_LOCALE_METHOD_NAMES.has(methodName) && !receiverIsProvablyDate;
32799
33314
  };
32800
- const matchLocaleMethodCall = (call) => {
33315
+ const matchLocaleMethodCall = (call, scopes) => {
32801
33316
  const callee = call.callee;
32802
33317
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
32803
33318
  if (!isNodeOfType(callee.property, "Identifier")) return null;
@@ -32805,7 +33320,7 @@ const matchLocaleMethodCall = (call) => {
32805
33320
  if (!LOCALE_FORMAT_METHOD_NAMES.has(methodName)) return null;
32806
33321
  const receiverIsProvablyDate = isProvableDateExpression(callee.object);
32807
33322
  if (methodName === "toLocaleString" && !receiverIsProvablyDate && !receiverNameLooksDateFlavored(callee.object)) return null;
32808
- if (isDeterministicLocaleMethodCall(call, methodName, receiverIsProvablyDate)) return null;
33323
+ if (isDeterministicLocaleMethodCall(call, methodName, receiverIsProvablyDate, scopes)) return null;
32809
33324
  return {
32810
33325
  node: call,
32811
33326
  display: `${methodName}()`
@@ -32821,13 +33336,13 @@ const getIntlFormatterName = (expression) => {
32821
33336
  if (!isNodeOfType(callee.property, "Identifier")) return null;
32822
33337
  return INTL_FORMATTER_NAMES.has(callee.property.name) ? callee.property.name : null;
32823
33338
  };
32824
- const isDeterministicIntlConstruction = (construction, formatterName) => {
33339
+ const isDeterministicIntlConstruction = (construction, formatterName, scopes) => {
32825
33340
  if (!isNodeOfType(construction, "CallExpression") && !isNodeOfType(construction, "NewExpression")) return false;
32826
- if (!hasExplicitLocaleArgument(construction.arguments?.[0])) return false;
33341
+ if (!hasExplicitLocaleArgument(construction.arguments?.[0], scopes)) return false;
32827
33342
  if (formatterName !== "DateTimeFormat") return true;
32828
- return objectLiteralHasProperty(construction.arguments?.[1], "timeZone");
33343
+ return objectHasExplicitProperty(construction.arguments?.[1], "timeZone", scopes, construction);
32829
33344
  };
32830
- const matchIntlFormatCall = (call) => {
33345
+ const matchIntlFormatCall = (call, scopes) => {
32831
33346
  const callee = call.callee;
32832
33347
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
32833
33348
  if (!isNodeOfType(callee.property, "Identifier")) return null;
@@ -32840,7 +33355,7 @@ const matchIntlFormatCall = (call) => {
32840
33355
  if (!construction) return null;
32841
33356
  const formatterName = getIntlFormatterName(construction);
32842
33357
  if (!formatterName) return null;
32843
- if (isDeterministicIntlConstruction(construction, formatterName)) return null;
33358
+ if (isDeterministicIntlConstruction(construction, formatterName, scopes)) return null;
32844
33359
  return {
32845
33360
  node: call,
32846
33361
  display: `Intl.${formatterName}().${callee.property.name}()`
@@ -32909,7 +33424,7 @@ const noLocaleFormatInRender = defineRule({
32909
33424
  fileIsEmailTemplate = hasEmailTemplateImport(node);
32910
33425
  },
32911
33426
  CallExpression(node) {
32912
- const match = matchLocaleMethodCall(node) ?? matchIntlFormatCall(node) ?? matchDateDefaultStringification(node);
33427
+ const match = matchLocaleMethodCall(node, context.scopes) ?? matchIntlFormatCall(node, context.scopes) ?? matchDateDefaultStringification(node);
32913
33428
  if (match) reportIfRenderPhase(match);
32914
33429
  },
32915
33430
  TemplateLiteral(node) {
@@ -32929,7 +33444,7 @@ const noLocaleFormatInRender = defineRule({
32929
33444
  walkAst(helperNode.body ?? helperNode, (child) => {
32930
33445
  if (isFunctionLike$1(child)) return false;
32931
33446
  if (!isNodeOfType(child, "CallExpression")) return;
32932
- const match = matchLocaleMethodCall(child) ?? matchIntlFormatCall(child);
33447
+ const match = matchLocaleMethodCall(child, context.scopes) ?? matchIntlFormatCall(child, context.scopes);
32933
33448
  if (!match || reportedNodes.has(match.node)) return;
32934
33449
  if (fileIsEmailTemplate) return;
32935
33450
  if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
@@ -36755,28 +37270,85 @@ const noRedundantShouldComponentUpdate = defineRule({
36755
37270
  });
36756
37271
  //#endregion
36757
37272
  //#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
36758
- const resolveReactRefSymbol = (memberExpression, scopes) => {
36759
- const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
36760
- if (!isNodeOfType(memberExpression, "MemberExpression") || memberExpression.computed || !isNodeOfType(memberExpression.property, "Identifier") || memberExpression.property.name !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
36761
- const symbol = resolveConstIdentifierAlias(receiver, scopes);
36762
- if (!symbol?.initializer) return null;
36763
- const initializer = stripParenExpression(symbol.initializer);
36764
- if (!isNodeOfType(initializer, "CallExpression")) return null;
36765
- return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
36766
- };
37273
+ const REPEATED_ANCESTOR_TYPES = new Set([
37274
+ "DoWhileStatement",
37275
+ "ForInStatement",
37276
+ "ForOfStatement",
37277
+ "ForStatement",
37278
+ "WhileStatement"
37279
+ ]);
36767
37280
  const isSameRefCurrentMember = (node, refSymbol, scopes) => {
36768
- if (!isNodeOfType(node, "MemberExpression") || node.computed || !isNodeOfType(node.property, "Identifier") || node.property.name !== "current") return false;
37281
+ if (!isNodeOfType(node, "MemberExpression") || getStaticPropertyName(node) !== "current") return false;
36769
37282
  const receiver = stripParenExpression(node.object);
36770
37283
  return isNodeOfType(receiver, "Identifier") && resolveConstIdentifierAlias(receiver, scopes)?.id === refSymbol.id;
36771
37284
  };
37285
+ const isSameRefCurrentAlias = (node, refSymbol, scopes) => {
37286
+ if (isSameRefCurrentMember(node, refSymbol, scopes)) return true;
37287
+ if (!isNodeOfType(node, "Identifier")) return false;
37288
+ const aliasSymbol = scopes.symbolFor(node);
37289
+ return aliasSymbol?.kind === "const" && aliasSymbol.initializer !== null && isSameRefCurrentMember(stripParenExpression(aliasSymbol.initializer), refSymbol, scopes);
37290
+ };
37291
+ const isEmptySentinel = (node, scopes) => isNodeOfType(node, "Literal") && node.value === null || isNodeOfType(node, "Identifier") && node.name === "undefined" && scopes.isGlobalReference(node);
37292
+ const hasRepeatedExecutionAncestor = (node, stop) => {
37293
+ let ancestor = node.parent;
37294
+ while (ancestor && ancestor !== stop) {
37295
+ if (isFunctionLike$1(ancestor) || REPEATED_ANCESTOR_TYPES.has(ancestor.type)) return true;
37296
+ ancestor = ancestor.parent;
37297
+ }
37298
+ return ancestor !== stop;
37299
+ };
37300
+ const getBranchConstraints = (node, branchRoot) => {
37301
+ const constraints = /* @__PURE__ */ new Map();
37302
+ let descendant = node;
37303
+ let ancestor = descendant.parent;
37304
+ while (ancestor && descendant !== branchRoot) {
37305
+ if (isNodeOfType(ancestor, "IfStatement")) {
37306
+ if (ancestor.consequent === descendant) constraints.set(ancestor, true);
37307
+ if (ancestor.alternate === descendant) constraints.set(ancestor, false);
37308
+ }
37309
+ descendant = ancestor;
37310
+ ancestor = ancestor.parent;
37311
+ }
37312
+ return constraints;
37313
+ };
37314
+ const canExecuteTogether = (firstConstraints, secondConstraints) => {
37315
+ for (const [statement, branch] of firstConstraints) {
37316
+ const otherBranch = secondConstraints.get(statement);
37317
+ if (otherBranch !== void 0 && otherBranch !== branch) return false;
37318
+ }
37319
+ return true;
37320
+ };
37321
+ const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol, scopes) => {
37322
+ const assignmentConstraints = getBranchConstraints(assignmentExpression, branchRoot);
37323
+ const assignmentStart = getRangeStart(assignmentExpression);
37324
+ let hasCoExecutableWrite = false;
37325
+ walkAst(branchRoot, (child) => {
37326
+ if (hasCoExecutableWrite) return false;
37327
+ const childStart = getRangeStart(child);
37328
+ if (child === assignmentExpression || !isNodeOfType(child, "AssignmentExpression") || assignmentStart === null || childStart === null || childStart >= assignmentStart || resolveReactRefSymbol(child.left, scopes)?.id !== refSymbol.id || hasRepeatedExecutionAncestor(child, branchRoot)) return;
37329
+ if (canExecuteTogether(assignmentConstraints, getBranchConstraints(child, branchRoot))) {
37330
+ hasCoExecutableWrite = true;
37331
+ return false;
37332
+ }
37333
+ });
37334
+ return !hasCoExecutableWrite;
37335
+ };
36772
37336
  const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes) => {
36773
- if (assignmentExpression.operator !== "=" || !isNodeOfType(assignmentExpression.right, "NewExpression")) return false;
37337
+ if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return true;
37338
+ if (assignmentExpression.operator !== "=") return false;
36774
37339
  let descendant = assignmentExpression;
36775
37340
  let ancestor = descendant.parent;
36776
37341
  while (ancestor) {
36777
- if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant && isNodeOfType(ancestor.test, "BinaryExpression") && (ancestor.test.operator === "===" || ancestor.test.operator === "==")) {
37342
+ if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(ancestor.test, "BinaryExpression") && [
37343
+ "===",
37344
+ "==",
37345
+ "!==",
37346
+ "!="
37347
+ ].includes(ancestor.test.operator)) {
36778
37348
  const { left, right } = ancestor.test;
36779
- if (isSameRefCurrentMember(left, refSymbol, scopes) && isNodeOfType(right, "Literal") && right.value === null || isSameRefCurrentMember(right, refSymbol, scopes) && isNodeOfType(left, "Literal") && left.value === null) return true;
37349
+ const comparesEmptySentinel = isSameRefCurrentAlias(left, refSymbol, scopes) && isEmptySentinel(right, scopes) || isSameRefCurrentAlias(right, refSymbol, scopes) && isEmptySentinel(left, scopes);
37350
+ const guardedBranch = ancestor.test.operator === "===" || ancestor.test.operator === "==" ? ancestor.consequent : ancestor.alternate;
37351
+ if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch && !hasRepeatedExecutionAncestor(assignmentExpression, guardedBranch) && hasNoPriorCoExecutableWrite(assignmentExpression, guardedBranch, refSymbol, scopes)) return true;
36780
37352
  }
36781
37353
  descendant = ancestor;
36782
37354
  ancestor = descendant.parent;
@@ -43685,13 +44257,13 @@ const resolveTanstackQueryHookName = (callExpression) => {
43685
44257
  }
43686
44258
  return null;
43687
44259
  };
43688
- const resolveHookNameFromInitializer = (initializer) => {
44260
+ const resolveHookNameFromInitializer = (initializer, scopes) => {
43689
44261
  if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
43690
44262
  if (!isNodeOfType(initializer, "Identifier")) return null;
43691
- const binding = findVariableInitializer(initializer, initializer.name);
43692
- if (!binding?.initializer || !isConstDeclaredBinding(binding)) return null;
43693
- if (!isNodeOfType(binding.initializer, "CallExpression")) return null;
43694
- return resolveTanstackQueryHookName(binding.initializer);
44263
+ const resolvedSymbol = resolveConstIdentifierAlias(initializer, scopes);
44264
+ if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
44265
+ if (!isNodeOfType(resolvedSymbol.initializer, "CallExpression")) return null;
44266
+ return resolveTanstackQueryHookName(resolvedSymbol.initializer);
43695
44267
  };
43696
44268
  const queryNoRestDestructuring = defineRule({
43697
44269
  id: "query-no-rest-destructuring",
@@ -43704,7 +44276,7 @@ const queryNoRestDestructuring = defineRule({
43704
44276
  if (!isNodeOfType(node.id, "ObjectPattern")) return;
43705
44277
  if (!node.init) return;
43706
44278
  if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
43707
- const hookName = resolveHookNameFromInitializer(node.init);
44279
+ const hookName = resolveHookNameFromInitializer(node.init, context.scopes);
43708
44280
  if (!hookName) return;
43709
44281
  context.report({
43710
44282
  node: node.id,
@@ -45059,7 +45631,7 @@ const DEFERRABLE_HOOK_NAMES = new Set([
45059
45631
  "useParams",
45060
45632
  "usePathname"
45061
45633
  ]);
45062
- const findHookCallBindings = (componentBody) => {
45634
+ const findHookCallBindings = (componentBody, scopes) => {
45063
45635
  const bindings = [];
45064
45636
  if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
45065
45637
  for (const statement of componentBody.body ?? []) {
@@ -45070,8 +45642,10 @@ const findHookCallBindings = (componentBody) => {
45070
45642
  const callee = declarator.init.callee;
45071
45643
  if (!isNodeOfType(callee, "Identifier")) continue;
45072
45644
  if (!DEFERRABLE_HOOK_NAMES.has(callee.name)) continue;
45645
+ const valueSymbol = scopes.symbolFor(declarator.id);
45646
+ if (!valueSymbol) continue;
45073
45647
  bindings.push({
45074
- valueName: declarator.id.name,
45648
+ valueSymbol,
45075
45649
  hookName: callee.name,
45076
45650
  declarator
45077
45651
  });
@@ -45079,6 +45653,49 @@ const findHookCallBindings = (componentBody) => {
45079
45653
  }
45080
45654
  return bindings;
45081
45655
  };
45656
+ const collectExactAliasSymbols = (componentBody, sourceSymbol, scopes) => {
45657
+ const symbols = [sourceSymbol];
45658
+ const symbolIds = new Set([sourceSymbol.id]);
45659
+ const aliasSourceIdentifiers = /* @__PURE__ */ new Set();
45660
+ if (!isNodeOfType(componentBody, "BlockStatement")) return {
45661
+ symbols,
45662
+ aliasSourceIdentifiers
45663
+ };
45664
+ let didFindAlias = true;
45665
+ while (didFindAlias) {
45666
+ didFindAlias = false;
45667
+ for (const statement of componentBody.body ?? []) {
45668
+ if (!isNodeOfType(statement, "VariableDeclaration") || statement.kind !== "const") continue;
45669
+ for (const declarator of statement.declarations ?? []) {
45670
+ let aliasIdentifier = null;
45671
+ let sourceIdentifier = null;
45672
+ const initializer = declarator.init ? stripParenExpression(declarator.init) : null;
45673
+ const arrayBinding = isNodeOfType(declarator.id, "ArrayPattern") ? declarator.id.elements[0] : null;
45674
+ const arrayValueNode = isNodeOfType(initializer, "ArrayExpression") ? initializer.elements[0] : null;
45675
+ const arrayValue = arrayValueNode && !isNodeOfType(arrayValueNode, "SpreadElement") ? stripParenExpression(arrayValueNode) : null;
45676
+ if (isNodeOfType(declarator.id, "Identifier") && isNodeOfType(initializer, "Identifier")) {
45677
+ aliasIdentifier = declarator.id;
45678
+ sourceIdentifier = initializer;
45679
+ } else if (isNodeOfType(declarator.id, "ArrayPattern") && declarator.id.elements.length === 1 && isNodeOfType(arrayBinding, "Identifier") && isNodeOfType(initializer, "ArrayExpression") && initializer.elements.length === 1 && isNodeOfType(arrayValue, "Identifier")) {
45680
+ aliasIdentifier = arrayBinding;
45681
+ sourceIdentifier = arrayValue;
45682
+ }
45683
+ if (!aliasIdentifier || !sourceIdentifier) continue;
45684
+ const referencedSymbol = scopes.symbolFor(sourceIdentifier);
45685
+ const aliasSymbol = scopes.symbolFor(aliasIdentifier);
45686
+ if (!referencedSymbol || !symbolIds.has(referencedSymbol.id) || !aliasSymbol || aliasSymbol.kind !== "const" || symbolIds.has(aliasSymbol.id)) continue;
45687
+ symbolIds.add(aliasSymbol.id);
45688
+ symbols.push(aliasSymbol);
45689
+ aliasSourceIdentifiers.add(sourceIdentifier);
45690
+ didFindAlias = true;
45691
+ }
45692
+ }
45693
+ }
45694
+ return {
45695
+ symbols,
45696
+ aliasSourceIdentifiers
45697
+ };
45698
+ };
45082
45699
  const rerenderDeferReadsHook = defineRule({
45083
45700
  id: "rerender-defer-reads-hook",
45084
45701
  title: "URL hook value only read in handlers",
@@ -45089,15 +45706,13 @@ const rerenderDeferReadsHook = defineRule({
45089
45706
  create: (context) => {
45090
45707
  const checkComponent = (componentBody) => {
45091
45708
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
45092
- const bindings = findHookCallBindings(componentBody);
45709
+ const bindings = findHookCallBindings(componentBody, context.scopes);
45093
45710
  if (bindings.length === 0) return;
45094
45711
  const handlerBindingNames = collectHandlerBindingNames(componentBody);
45095
45712
  for (const binding of bindings) {
45713
+ const { symbols, aliasSourceIdentifiers } = collectExactAliasSymbols(componentBody, binding.valueSymbol, context.scopes);
45096
45714
  const referenceLocations = [];
45097
- walkAst(componentBody, (child) => {
45098
- if (isNodeOfType(binding.declarator, "VariableDeclarator") && child === binding.declarator.id) return;
45099
- if (isNodeOfType(child, "Identifier") && child.name === binding.valueName) referenceLocations.push(child);
45100
- });
45715
+ for (const symbol of symbols) for (const reference of symbol.references) if (!aliasSourceIdentifiers.has(reference.identifier)) referenceLocations.push(reference.identifier);
45101
45716
  if (referenceLocations.length === 0) continue;
45102
45717
  if (!referenceLocations.every((ref) => isInsideEventHandler(ref, handlerBindingNames))) continue;
45103
45718
  context.report({
@@ -53046,7 +53661,8 @@ const serverCacheWithObjectLiteral = defineRule({
53046
53661
  if (!isNodeOfType(node.callee, "Identifier")) return;
53047
53662
  if (!cachedFunctionNames.has(node.callee.name)) return;
53048
53663
  const firstArg = node.arguments?.[0];
53049
- if (!isNodeOfType(firstArg, "ObjectExpression")) return;
53664
+ if (!firstArg || isNodeOfType(firstArg, "SpreadElement")) return;
53665
+ if (!isNodeOfType(unwrapObjectIntegrityExpression(firstArg, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES), "ObjectExpression")) return;
53050
53666
  context.report({
53051
53667
  node,
53052
53668
  message: `Passing a new object to React.cache() each render misses the cache, so it refetches every request.`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.6-dev.4fb3694",
3
+ "version": "0.7.6-dev.543ebfe",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",