oxlint-plugin-react-doctor 0.7.6-dev.47beb25 → 0.7.6-dev.4fb3694

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 +109 -728
  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.ts
1184
- const getRootIdentifier$1 = (node, options) => {
1183
+ //#region src/plugin/utils/get-root-identifier-name.ts
1184
+ const getRootIdentifierName = (node, options) => {
1185
1185
  if (!node) return null;
1186
1186
  const followCallChains = options?.followCallChains === true;
1187
1187
  let cursor = node;
@@ -1192,18 +1192,16 @@ const getRootIdentifier$1 = (node, options) => {
1192
1192
  continue;
1193
1193
  }
1194
1194
  if (followCallChains && isNodeOfType(cursor, "CallExpression")) {
1195
- if (!isNodeOfType(cursor.callee, "MemberExpression")) return null;
1196
- cursor = cursor.callee.object;
1195
+ const callee = cursor.callee;
1196
+ if (!isNodeOfType(callee, "MemberExpression")) return null;
1197
+ cursor = callee.object;
1197
1198
  continue;
1198
1199
  }
1199
1200
  break;
1200
1201
  }
1201
- return isNodeOfType(cursor, "Identifier") ? cursor : null;
1202
+ return isNodeOfType(cursor, "Identifier") ? cursor.name : null;
1202
1203
  };
1203
1204
  //#endregion
1204
- //#region src/plugin/utils/get-root-identifier-name.ts
1205
- const getRootIdentifierName = (node, options) => getRootIdentifier$1(node, options)?.name ?? null;
1206
- //#endregion
1207
1205
  //#region src/plugin/rules/state-and-effects/advanced-event-handler-refs.ts
1208
1206
  const STABLE_HANDLER_HOOK_NAMES = new Set([
1209
1207
  "useCallback",
@@ -5964,7 +5962,7 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
5964
5962
  //#endregion
5965
5963
  //#region src/plugin/utils/get-static-property-key-name.ts
5966
5964
  const getStaticPropertyKeyName = (node, options = {}) => {
5967
- if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition")) return null;
5965
+ if (!isNodeOfType(node, "Property")) return null;
5968
5966
  if (node.computed) {
5969
5967
  if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
5970
5968
  return null;
@@ -6023,7 +6021,7 @@ const isPureEventBlockerHandler = (attribute) => {
6023
6021
  //#endregion
6024
6022
  //#region src/plugin/utils/resolve-const-identifier-alias.ts
6025
6023
  const resolveConstIdentifierAlias = (identifier, scopes) => {
6026
- if (!isNodeOfType(identifier, "Identifier") && !isNodeOfType(identifier, "JSXIdentifier")) return null;
6024
+ if (!isNodeOfType(identifier, "Identifier")) return null;
6027
6025
  const visitedSymbolIds = /* @__PURE__ */ new Set();
6028
6026
  let symbol = scopes.symbolFor(identifier);
6029
6027
  while (symbol?.kind === "const") {
@@ -6298,131 +6296,6 @@ const isGlobalMethodCall = (node, objectName, methodName) => {
6298
6296
  return isNodeOfType(receiver, "Identifier") && receiver.name === objectName && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
6299
6297
  };
6300
6298
  //#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
6426
6299
  //#region src/plugin/rules/client/client-localstorage-no-version.ts
6427
6300
  const VERSIONED_KEY_PATTERN = /(?:[._:-]v\d+|@\d+|\bv\d+\b)/i;
6428
6301
  const CAMEL_CASE_VERSIONED_KEY_PATTERN = /[a-z]V\d+/;
@@ -6444,39 +6317,26 @@ const clientLocalstorageNoVersion = defineRule({
6444
6317
  severity: "warn",
6445
6318
  category: "Correctness",
6446
6319
  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.",
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
- }
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
+ } })
6480
6340
  });
6481
6341
  //#endregion
6482
6342
  //#region src/plugin/rules/client/client-passive-event-listeners.ts
@@ -16112,21 +15972,14 @@ const jsxFilenameExtension = defineRule({
16112
15972
  });
16113
15973
  //#endregion
16114
15974
  //#region src/plugin/utils/is-jsx-fragment-element.ts
16115
- const isJsxFragmentElement = (node, scopes) => {
15975
+ const isJsxFragmentElement = (node) => {
16116
15976
  if (!isNodeOfType(node, "JSXOpeningElement")) return false;
16117
15977
  const elementName = node.name;
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
- }
15978
+ if (isNodeOfType(elementName, "JSXIdentifier")) return elementName.name === "Fragment";
16124
15979
  if (isNodeOfType(elementName, "JSXMemberExpression")) {
16125
15980
  if (!isNodeOfType(elementName.object, "JSXIdentifier")) return false;
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);
15981
+ if (elementName.object.name !== "React") return false;
15982
+ return elementName.property.name === "Fragment";
16130
15983
  }
16131
15984
  return false;
16132
15985
  };
@@ -16152,7 +16005,7 @@ const jsxFragments = defineRule({
16152
16005
  if (mode !== "syntax") return;
16153
16006
  if (!node.closingElement) return;
16154
16007
  const openingElement = node.openingElement;
16155
- if (!isJsxFragmentElement(openingElement, context.scopes)) return;
16008
+ if (!isJsxFragmentElement(openingElement)) return;
16156
16009
  if (openingElement.attributes.length > 0) return;
16157
16010
  context.report({
16158
16011
  node: openingElement,
@@ -19293,7 +19146,7 @@ const jsxNoUselessFragment = defineRule({
19293
19146
  return {
19294
19147
  JSXElement(node) {
19295
19148
  const openingElement = node.openingElement;
19296
- if (!isJsxFragmentElement(openingElement, context.scopes)) return;
19149
+ if (!isJsxFragmentElement(openingElement)) return;
19297
19150
  if (hasJsxKeyAttribute(openingElement)) return;
19298
19151
  if (checkChildren(node, openingElement, node.children)) return;
19299
19152
  if (isChildOfHtmlElement(node)) context.report({
@@ -20535,24 +20388,8 @@ const hasDirective = (programNode, directive) => {
20535
20388
  return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
20536
20389
  };
20537
20390
  //#endregion
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
- };
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"));
20556
20393
  //#endregion
20557
20394
  //#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
20558
20395
  const nextjsAsyncClientComponent = defineRule({
@@ -20578,10 +20415,10 @@ const nextjsAsyncClientComponent = defineRule({
20578
20415
  },
20579
20416
  VariableDeclarator(node) {
20580
20417
  if (!fileHasUseClient) return;
20418
+ if (!isComponentAssignment(node)) return;
20419
+ if (!isInlineFunctionExpression(node.init)) return;
20420
+ if (!node.init.async) return;
20581
20421
  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;
20585
20422
  context.report({
20586
20423
  node,
20587
20424
  message: `Async client component "${node.id.name}" fails to render because client components can't be async.`
@@ -21401,7 +21238,6 @@ const nextjsNoNativeScript = defineRule({
21401
21238
  });
21402
21239
  //#endregion
21403
21240
  //#region src/plugin/rules/nextjs/nextjs-no-polyfill-script.ts
21404
- const ABSOLUTE_URL_SCHEME_PATTERN = /^[a-z][a-z\d+.-]*:/i;
21405
21241
  const nextjsNoPolyfillScript = defineRule({
21406
21242
  id: "nextjs-no-polyfill-script",
21407
21243
  title: "Redundant polyfill script",
@@ -21415,9 +21251,7 @@ const nextjsNoPolyfillScript = defineRule({
21415
21251
  const srcAttribute = findJsxAttribute(node.attributes ?? [], "src");
21416
21252
  if (!srcAttribute?.value) return;
21417
21253
  const srcValue = isNodeOfType(srcAttribute.value, "Literal") ? srcAttribute.value.value : null;
21418
- const requestUrl = typeof srcValue === "string" ? srcValue.split("#", 1)[0] : null;
21419
- const requestScheme = requestUrl?.trimStart().match(ABSOLUTE_URL_SCHEME_PATTERN)?.[0].toLowerCase();
21420
- if (requestUrl && (!requestScheme || requestScheme === "http:" || requestScheme === "https:") && POLYFILL_SCRIPT_PATTERN.test(requestUrl)) context.report({
21254
+ if (typeof srcValue === "string" && POLYFILL_SCRIPT_PATTERN.test(srcValue)) context.report({
21421
21255
  node,
21422
21256
  message: "This polyfill CDN script makes your users download polyfills Next.js already includes."
21423
21257
  });
@@ -26320,9 +26154,6 @@ const functionContainsReactRenderOutput = (functionNode, scopes) => {
26320
26154
  return hasRenderOutput;
26321
26155
  };
26322
26156
  //#endregion
26323
- //#region src/plugin/utils/is-component-assignment.ts
26324
- 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"));
26325
- //#endregion
26326
26157
  //#region src/plugin/utils/is-component-declaration.ts
26327
26158
  const isComponentDeclaration = (node) => isNodeOfType(node, "FunctionDeclaration") && node.id !== null && Boolean(node.id?.name) && isUppercaseName(node.id.name);
26328
26159
  //#endregion
@@ -31964,26 +31795,6 @@ const noImgLazyWithHighFetchpriority = defineRule({
31964
31795
  } })
31965
31796
  });
31966
31797
  //#endregion
31967
- //#region src/plugin/utils/react-ref-origin.ts
31968
- const resolveReactRefSymbol = (memberExpression, scopes) => {
31969
- const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
31970
- if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticPropertyName(memberExpression) !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
31971
- const symbol = resolveConstIdentifierAlias(receiver, scopes);
31972
- if (!symbol?.initializer) return null;
31973
- const initializer = stripParenExpression(symbol.initializer);
31974
- if (!isNodeOfType(initializer, "CallExpression")) return null;
31975
- return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
31976
- };
31977
- const hasReactRefCurrentOrigin = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
31978
- const expression = stripParenExpression(node);
31979
- if (resolveReactRefSymbol(expression, scopes)) return true;
31980
- if (!isNodeOfType(expression, "Identifier")) return false;
31981
- const symbol = resolveConstIdentifierAlias(expression, scopes);
31982
- if (!symbol?.initializer || visitedSymbolIds.has(symbol.id)) return false;
31983
- visitedSymbolIds.add(symbol.id);
31984
- return hasReactRefCurrentOrigin(symbol.initializer, scopes, visitedSymbolIds);
31985
- };
31986
- //#endregion
31987
31798
  //#region src/plugin/rules/state-and-effects/no-impure-state-updater.ts
31988
31799
  const TIMER_FUNCTION_NAMES = new Set([
31989
31800
  "cancelAnimationFrame",
@@ -32015,33 +31826,6 @@ const NOTIFICATION_METHOD_NAMES = new Set([
32015
31826
  "success",
32016
31827
  "warning"
32017
31828
  ]);
32018
- const NOTIFICATION_MODULE_SOURCES = new Set([
32019
- "@chakra-ui/react",
32020
- "@heroui/react",
32021
- "@mantine/notifications",
32022
- "antd",
32023
- "react-hot-toast",
32024
- "react-toastify",
32025
- "sonner"
32026
- ]);
32027
- const SYNCHRONOUS_ARRAY_METHOD_NAMES = new Set([
32028
- "every",
32029
- "filter",
32030
- "find",
32031
- "findIndex",
32032
- "flatMap",
32033
- "forEach",
32034
- "map",
32035
- "reduce",
32036
- "reduceRight",
32037
- "some",
32038
- "sort"
32039
- ]);
32040
- const isNotificationModuleSource = (source) => {
32041
- if (!source) return false;
32042
- for (const moduleSource of NOTIFICATION_MODULE_SOURCES) if (source === moduleSource || source.startsWith(`${moduleSource}/`)) return true;
32043
- return /(?:^|[/_.-])(?:notification|toast)s?(?:$|[/_.-])/i.test(source);
32044
- };
32045
31829
  const getMemberCall = (node) => {
32046
31830
  if (!isNodeOfType(node, "CallExpression")) return null;
32047
31831
  if (!isNodeOfType(node.callee, "MemberExpression") || node.callee.computed) return null;
@@ -32053,37 +31837,27 @@ const getMemberCall = (node) => {
32053
31837
  };
32054
31838
  const isNotificationReceiver = (receiver, scopes) => {
32055
31839
  if (!isNodeOfType(receiver, "Identifier")) return false;
32056
- const importBinding = getImportBindingForName(receiver, receiver.name);
32057
- if (importBinding) return isNotificationModuleSource(importBinding.source) && (importBinding.exportedName === "default" || importBinding.exportedName !== null && NOTIFICATION_RECEIVER_NAMES.has(importBinding.exportedName));
31840
+ if (!NOTIFICATION_RECEIVER_NAMES.has(receiver.name)) return false;
32058
31841
  const symbol = scopes.symbolFor(receiver);
31842
+ if (symbol?.kind === "import") return true;
32059
31843
  if (!isNodeOfType(symbol?.initializer, "CallExpression")) return false;
32060
31844
  const callee = symbol.initializer.callee;
32061
- if (!isNodeOfType(callee, "Identifier")) return false;
32062
- const hookImport = getImportBindingForName(callee, callee.name);
32063
- return Boolean(hookImport && isNotificationModuleSource(hookImport.source) && hookImport.exportedName !== null && /^use(?:Message|Notification|Toast)$/.test(hookImport.exportedName));
32064
- };
32065
- const isKnownGlobalObject = (node, expectedName, scopes) => isNodeOfType(node, "Identifier") && node.name === expectedName && scopes.isGlobalReference(node);
32066
- 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));
32067
- const isStorageReceiver = (node, scopes) => {
32068
- if (isNodeOfType(node, "Identifier") && STORAGE_RECEIVER_NAMES.has(node.name) && scopes.isGlobalReference(node)) return true;
32069
- for (const receiverName of STORAGE_RECEIVER_NAMES) if (isGlobalMember(node, receiverName, scopes)) return true;
32070
- return false;
31845
+ return isNodeOfType(callee, "Identifier") && /^use(?:Message|Notification|Toast)$/.test(callee.name);
32071
31846
  };
32072
31847
  const getKnownImpureCall = (callExpression, scopes) => {
32073
31848
  if (isNodeOfType(callExpression.callee, "Identifier") && TIMER_FUNCTION_NAMES.has(callExpression.callee.name) && scopes.isGlobalReference(callExpression.callee)) return `${callExpression.callee.name}()`;
32074
- 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}()`;
32075
31849
  const memberCall = getMemberCall(callExpression);
32076
31850
  if (!memberCall) return null;
32077
31851
  const { methodName, receiver } = memberCall;
32078
- if (STORAGE_MUTATION_METHOD_NAMES.has(methodName) && isStorageReceiver(receiver, scopes)) return `${methodName}()`;
32079
- if (EXTERNAL_READ_METHOD_NAMES.has(methodName) && hasReactRefCurrentOrigin(receiver, scopes)) return `.${methodName}()`;
32080
- const receiverRoot = getRootIdentifier$1(receiver);
32081
- if (EXTERNAL_READ_METHOD_NAMES.has(methodName) && receiverRoot !== null && isKnownGlobalObject(receiverRoot, "document", scopes)) return `.${methodName}()`;
32082
- if (NOTIFICATION_METHOD_NAMES.has(methodName) && isNotificationReceiver(receiver, scopes)) return `${methodName}()`;
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}()`;
32083
31855
  return null;
32084
31856
  };
32085
31857
  const getExternalAssignmentDescription = (assignmentTarget, updater, scopes) => {
32086
- const rootIdentifier = getRootIdentifier$1(assignmentTarget);
31858
+ let rootIdentifier = null;
31859
+ if (isNodeOfType(assignmentTarget, "Identifier")) rootIdentifier = assignmentTarget;
31860
+ else if (isNodeOfType(assignmentTarget, "MemberExpression") && isNodeOfType(assignmentTarget.object, "Identifier")) rootIdentifier = assignmentTarget.object;
32087
31861
  if (!rootIdentifier) return null;
32088
31862
  const updaterScope = scopes.ownScopeFor(updater);
32089
31863
  if (!updaterScope) return null;
@@ -32092,26 +31866,12 @@ const getExternalAssignmentDescription = (assignmentTarget, updater, scopes) =>
32092
31866
  if (symbol.kind === "parameter" && symbol.scope === updaterScope) return `the updater argument "${rootIdentifier.name}"`;
32093
31867
  return isDescendantScope(symbol.scope, updaterScope) ? null : `the captured value "${rootIdentifier.name}"`;
32094
31868
  };
32095
- const isArrayValue = (node, scopes) => {
32096
- const expression = stripParenExpression(node);
32097
- if (isNodeOfType(expression, "ArrayExpression")) return true;
32098
- if (!isNodeOfType(expression, "Identifier")) return false;
32099
- return isNodeOfType(resolveConstIdentifierAlias(expression, scopes)?.initializer, "ArrayExpression");
32100
- };
32101
- const isDefinitelySynchronousCallback = (callback, scopes) => {
32102
- const parent = callback.parent;
32103
- if (!parent) return false;
32104
- if (isNodeOfType(parent, "CallExpression") && parent.callee === callback) return true;
32105
- if (!isNodeOfType(parent, "CallExpression") || !parent.arguments?.some((argument) => argument === callback)) return false;
32106
- 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;
32107
- 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;
32108
- };
32109
31869
  const findImpureUpdaterOperation = (updater, scopes) => {
32110
31870
  const analysis = getProgramAnalysis(updater);
32111
31871
  let operation = null;
32112
31872
  walkAst(updater, (child) => {
32113
31873
  if (operation) return false;
32114
- if (child !== updater && isFunctionLike$1(child) && !isDefinitelySynchronousCallback(child, scopes)) return false;
31874
+ if (child !== updater && isFunctionLike$1(child) && !executesDuringRender(child, scopes)) return false;
32115
31875
  if (isNodeOfType(child, "CallExpression")) {
32116
31876
  if (isNodeOfType(child.callee, "Identifier") && analysis) {
32117
31877
  const calleeReference = getRef(analysis, child.callee);
@@ -32143,25 +31903,18 @@ const noImpureStateUpdater = defineRule({
32143
31903
  severity: "error",
32144
31904
  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.",
32145
31905
  create: (context) => ({ CallExpression(node) {
32146
- const updaterArgument = node.arguments?.[0];
32147
- if (!updaterArgument) return;
31906
+ const updater = node.arguments?.[0];
31907
+ if (!updater || !isFunctionLike$1(updater)) return;
32148
31908
  const analysis = getProgramAnalysis(node);
32149
31909
  if (!analysis || !isNodeOfType(node.callee, "Identifier")) return;
32150
31910
  const calleeReference = getRef(analysis, node.callee);
32151
31911
  if (!calleeReference || !isStateSetterCall(analysis, calleeReference)) return;
32152
31912
  const stateDeclarator = getUseStateDecl(analysis, calleeReference);
32153
31913
  if (!isNodeOfType(stateDeclarator, "VariableDeclarator") || !isNodeOfType(stateDeclarator.init, "CallExpression") || !isReactApiCall(stateDeclarator.init, "useState", context.scopes, { allowGlobalReactNamespace: true })) return;
32154
- let updater = null;
32155
- if (isFunctionLike$1(updaterArgument)) updater = updaterArgument;
32156
- else if (isNodeOfType(updaterArgument, "Identifier")) {
32157
- const updaterReference = getRef(analysis, updaterArgument);
32158
- if (updaterReference) updater = resolveToFunction(updaterReference);
32159
- }
32160
- if (!updater) return;
32161
31914
  const operation = findImpureUpdaterOperation(updater, context.scopes);
32162
31915
  if (!operation) return;
32163
31916
  context.report({
32164
- node: updaterArgument,
31917
+ node: updater,
32165
31918
  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.`
32166
31919
  });
32167
31920
  } })
@@ -32450,12 +32203,11 @@ const isMemoCall = (node) => {
32450
32203
  };
32451
32204
  const isDefaultEquivalentComparator = (comparator) => isNodeOfType(comparator, "Identifier") && (comparator.name === "undefined" || comparator.name === "shallowEqual");
32452
32205
  const hasCustomComparator = (node) => isNodeOfType(node, "CallExpression") && (node.arguments?.length ?? 0) >= 2 && !isDefaultEquivalentComparator(node.arguments?.[1]);
32453
- const isInlineReference = (node, scopes) => {
32454
- const referenceNode = unwrapObjectIntegrityExpression(node, scopes);
32455
- 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";
32456
- if (isNodeOfType(referenceNode, "ObjectExpression")) return "objects";
32457
- if (isNodeOfType(referenceNode, "ArrayExpression")) return "Arrays";
32458
- if (isNodeOfType(referenceNode, "JSXElement") || isNodeOfType(referenceNode, "JSXFragment")) return "JSX";
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";
32459
32211
  return null;
32460
32212
  };
32461
32213
  const noInlinePropOnMemoComponent = defineRule({
@@ -32485,7 +32237,7 @@ const noInlinePropOnMemoComponent = defineRule({
32485
32237
  let elementName = null;
32486
32238
  if (isNodeOfType(openingElement.name, "JSXIdentifier")) elementName = openingElement.name.name;
32487
32239
  if (!elementName || !memoizedComponentNames.has(elementName)) return;
32488
- const propType = isInlineReference(node.value.expression, context.scopes);
32240
+ const propType = isInlineReference(node.value.expression);
32489
32241
  if (propType) context.report({
32490
32242
  node: node.value.expression,
32491
32243
  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`
@@ -33006,18 +32758,6 @@ const INTL_FORMAT_METHOD_NAMES = new Set([
33006
32758
  "formatToParts",
33007
32759
  "formatRange"
33008
32760
  ]);
33009
- const ABSENT_PROPERTY_PROOF = { status: "absent" };
33010
- const PRESENT_PROPERTY_PROOF = { status: "present" };
33011
- const UNDEFINED_PROPERTY_PROOF = { status: "undefined" };
33012
- const UNKNOWN_PROPERTY_PROOF = { status: "unknown" };
33013
- const READ_ONLY_OBJECT_METHOD_NAMES = new Set([
33014
- "hasOwnProperty",
33015
- "isPrototypeOf",
33016
- "propertyIsEnumerable",
33017
- "toLocaleString",
33018
- "toString",
33019
- "valueOf"
33020
- ]);
33021
32761
  const isProvableDateExpression = (expression) => {
33022
32762
  if (!expression) return false;
33023
32763
  const unwrapped = stripParenExpression(expression);
@@ -33032,290 +32772,32 @@ const receiverNameLooksDateFlavored = (expression) => {
33032
32772
  if (isNodeOfType(unwrapped, "CallExpression")) return receiverNameLooksDateFlavored(unwrapped.callee);
33033
32773
  return false;
33034
32774
  };
33035
- const isStaticUndefined = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
33036
- const expression = stripParenExpression(node);
33037
- if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return true;
33038
- if (!isNodeOfType(expression, "Identifier")) return false;
33039
- if (expression.name === "undefined" && scopes.isGlobalReference(expression)) return true;
33040
- const symbol = scopes.symbolFor(expression);
33041
- if (symbol?.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || visitedSymbolIds.has(symbol.id)) return false;
33042
- visitedSymbolIds.add(symbol.id);
33043
- return isStaticUndefined(symbol.initializer, scopes, visitedSymbolIds);
33044
- };
33045
- const isNestedAssignmentTarget = (expression) => {
33046
- let target = expression;
33047
- let parent = target.parent;
33048
- while (parent) {
33049
- if (isNodeOfType(parent, "AssignmentExpression")) return parent.left === target;
33050
- if (isNodeOfType(parent, "ForInStatement") || isNodeOfType(parent, "ForOfStatement")) return parent.left === target;
33051
- if (isNodeOfType(parent, "AssignmentPattern")) {
33052
- if (parent.left !== target) return false;
33053
- } else if (isNodeOfType(parent, "RestElement")) {
33054
- if (parent.argument !== target) return false;
33055
- } else if (isNodeOfType(parent, "ArrayPattern")) {
33056
- if (!parent.elements?.some((element) => element === target)) return false;
33057
- } else if (isNodeOfType(parent, "Property")) {
33058
- if (parent.value !== target || !isNodeOfType(parent.parent, "ObjectPattern")) return false;
33059
- } else if (isNodeOfType(parent, "ObjectPattern")) {
33060
- if (!parent.properties?.some((property) => property === target)) return false;
33061
- } else return false;
33062
- target = parent;
33063
- parent = target.parent;
33064
- }
33065
- return false;
33066
- };
33067
- const isPotentialMutationReference = (identifier, readNode) => {
33068
- let expression = identifier;
33069
- let parent = expression.parent;
33070
- while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === expression) {
33071
- expression = parent;
33072
- parent = expression.parent;
33073
- }
33074
- const rootExpression = expression;
33075
- let memberDepth = 0;
33076
- while (parent && isNodeOfType(parent, "MemberExpression") && parent.object === expression) {
33077
- memberDepth += 1;
33078
- expression = parent;
33079
- parent = expression.parent;
33080
- while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === expression) {
33081
- expression = parent;
33082
- parent = expression.parent;
33083
- }
33084
- }
33085
- if (!parent || isAstDescendant(identifier, readNode)) return false;
33086
- if (isNestedAssignmentTarget(expression)) return true;
33087
- if (isNodeOfType(parent, "AssignmentExpression") && parent.left === expression) return true;
33088
- if (isNodeOfType(parent, "UpdateExpression") && parent.argument === expression) return true;
33089
- if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expression) return true;
33090
- if (isNodeOfType(parent, "CallExpression")) {
33091
- if (parent.callee === expression) {
33092
- if (memberDepth === 0) return true;
33093
- const memberExpression = stripParenExpression(expression);
33094
- return memberDepth === 1 && isNodeOfType(memberExpression, "MemberExpression") && !READ_ONLY_OBJECT_METHOD_NAMES.has(getStaticPropertyName(memberExpression) ?? "");
33095
- }
33096
- return memberDepth === 0 && parent.arguments?.some((argument) => argument === rootExpression);
33097
- }
33098
- if (isNodeOfType(parent, "NewExpression")) return memberDepth === 0 && parent.arguments?.some((argument) => argument === rootExpression);
33099
- if (isNodeOfType(parent, "AssignmentExpression") && parent.right === rootExpression) return true;
33100
- return false;
33101
- };
33102
- const getSimpleAlias = (identifier, scopes) => {
33103
- let expression = identifier;
33104
- let parent = expression.parent;
33105
- while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === expression) {
33106
- expression = parent;
33107
- parent = expression.parent;
33108
- }
33109
- if (!isNodeOfType(parent, "VariableDeclarator") || parent.init !== expression || !isNodeOfType(parent.id, "Identifier")) return null;
33110
- const symbol = scopes.symbolFor(parent.id);
33111
- return symbol ? {
33112
- symbol,
33113
- readNode: parent.id
33114
- } : null;
33115
- };
33116
- const getDirectCallForExpression = (expression) => {
33117
- let callee = expression;
33118
- let parent = callee.parent;
33119
- while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === callee) {
33120
- callee = parent;
33121
- parent = callee.parent;
33122
- }
33123
- return isNodeOfType(parent, "CallExpression") && parent.callee === callee ? parent : null;
33124
- };
33125
- const getMethodOwner = (functionNode, scopes) => {
33126
- const methodNode = functionNode.parent;
33127
- if (isNodeOfType(methodNode, "Property") && methodNode.value === functionNode && isNodeOfType(methodNode.parent, "ObjectExpression")) {
33128
- const objectParent = methodNode.parent.parent;
33129
- if (!isNodeOfType(objectParent, "VariableDeclarator") || objectParent.init !== methodNode.parent || !isNodeOfType(objectParent.id, "Identifier")) return null;
33130
- const ownerSymbol = scopes.symbolFor(objectParent.id);
33131
- const methodName = getStaticPropertyKeyName(methodNode, { allowComputedString: true });
33132
- return ownerSymbol && methodName ? {
33133
- methodName,
33134
- ownerKind: "object",
33135
- ownerSymbol,
33136
- isStatic: false
33137
- } : null;
33138
- }
33139
- if (!isNodeOfType(methodNode, "MethodDefinition") || methodNode.value !== functionNode || !isNodeOfType(methodNode.parent, "ClassBody")) return null;
33140
- const classNode = methodNode.parent.parent;
33141
- if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression")) return null;
33142
- let bindingIdentifier = isNodeOfType(classNode.id, "Identifier") ? classNode.id : null;
33143
- if (!bindingIdentifier && isNodeOfType(classNode.parent, "VariableDeclarator")) bindingIdentifier = isNodeOfType(classNode.parent.id, "Identifier") ? classNode.parent.id : null;
33144
- if (!bindingIdentifier) return null;
33145
- const ownerSymbol = scopes.symbolFor(bindingIdentifier);
33146
- const methodName = getStaticPropertyKeyName(methodNode, { allowComputedString: true });
33147
- return ownerSymbol && methodName ? {
33148
- methodName,
33149
- ownerKind: "class",
33150
- ownerSymbol,
33151
- isStatic: Boolean(methodNode.static)
33152
- } : null;
33153
- };
33154
- const doesSymbolResolveToOwner = (symbol, ownerSymbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
33155
- if (!symbol) return false;
33156
- if (symbol.declarationNode === ownerSymbol.declarationNode) return true;
33157
- if (symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id)) return false;
33158
- visitedSymbolIds.add(symbol.id);
33159
- const initializer = stripParenExpression(symbol.initializer);
33160
- return isNodeOfType(initializer, "Identifier") && doesSymbolResolveToOwner(scopes.symbolFor(initializer), ownerSymbol, scopes, visitedSymbolIds);
33161
- };
33162
- const isClassInstanceExpression = (expression, ownerSymbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
33163
- const candidate = stripParenExpression(expression);
33164
- if (isNodeOfType(candidate, "NewExpression") && isNodeOfType(candidate.callee, "Identifier")) return doesSymbolResolveToOwner(scopes.symbolFor(candidate.callee), ownerSymbol, scopes);
33165
- if (!isNodeOfType(candidate, "Identifier")) return false;
33166
- const symbol = scopes.symbolFor(candidate);
33167
- if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id)) return false;
33168
- visitedSymbolIds.add(symbol.id);
33169
- return isClassInstanceExpression(symbol.initializer, ownerSymbol, scopes, visitedSymbolIds);
33170
- };
33171
- const getMethodCalls = (functionNode, scopes) => {
33172
- const owner = getMethodOwner(functionNode, scopes);
33173
- if (!owner) return [];
33174
- const calls = [];
33175
- walkAst(scopes.rootScope.node, (child) => {
33176
- if (!isNodeOfType(child, "MemberExpression")) return;
33177
- if (getStaticPropertyName(child) !== owner.methodName) return;
33178
- const receiver = stripParenExpression(child.object);
33179
- let doesReceiverMatch = isNodeOfType(receiver, "Identifier") && doesSymbolResolveToOwner(scopes.symbolFor(receiver), owner.ownerSymbol, scopes);
33180
- if (owner.ownerKind === "class" && !owner.isStatic) doesReceiverMatch = isClassInstanceExpression(receiver, owner.ownerSymbol, scopes);
33181
- if (!doesReceiverMatch) return;
33182
- const call = getDirectCallForExpression(child);
33183
- if (call) calls.push(call);
33184
- });
33185
- return calls;
33186
- };
33187
- const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, scopes, visitedSymbolIds, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
33188
- if (visitedFunctionNodes.has(functionNode)) return false;
33189
- visitedFunctionNodes.add(functionNode);
33190
- const usageFunction = findEnclosingFunction(usageNode);
33191
- const immediateCall = getDirectCallForExpression(functionNode);
33192
- if (immediateCall) {
33193
- const immediateCallFunction = findEnclosingFunction(immediateCall);
33194
- if (immediateCallFunction === usageFunction) {
33195
- const immediateCallStart = getRangeStart(immediateCall);
33196
- return immediateCallStart === null || immediateCallStart < usageBoundary;
33197
- }
33198
- if (!immediateCallFunction) return usageFunction !== null;
33199
- return isFunctionInvokedBeforeUsage(immediateCallFunction, usageNode, usageBoundary, scopes, visitedSymbolIds, new Set(visitedFunctionNodes));
33200
- }
33201
- for (const methodCall of getMethodCalls(functionNode, scopes)) {
33202
- const methodCallFunction = findEnclosingFunction(methodCall);
33203
- if (methodCallFunction === usageFunction) {
33204
- const methodCallStart = getRangeStart(methodCall);
33205
- if (methodCallStart === null || methodCallStart < usageBoundary) return true;
33206
- continue;
33207
- }
33208
- if (!methodCallFunction) {
33209
- if (usageFunction) return true;
33210
- continue;
33211
- }
33212
- if (isFunctionInvokedBeforeUsage(methodCallFunction, usageNode, usageBoundary, scopes, new Set(visitedSymbolIds), new Set(visitedFunctionNodes))) return true;
33213
- }
33214
- const bindingIdentifier = getFunctionBindingIdentifier(functionNode);
33215
- if (!bindingIdentifier) return false;
33216
- const symbol = scopes.symbolFor(bindingIdentifier);
33217
- if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
33218
- visitedSymbolIds.add(symbol.id);
33219
- let wasInvokedBeforeUsage = false;
33220
- walkAst(scopes.rootScope.node, (child) => {
33221
- if (wasInvokedBeforeUsage || !isNodeOfType(child, "Identifier")) return;
33222
- if (scopes.symbolFor(child)?.declarationNode !== symbol.declarationNode) return;
33223
- const call = getDirectCallForExpression(child);
33224
- if (!call) return false;
33225
- const callFunction = findEnclosingFunction(call);
33226
- if (callFunction === usageFunction) {
33227
- const callStart = getRangeStart(call);
33228
- wasInvokedBeforeUsage = callStart === null || callStart < usageBoundary;
33229
- return;
33230
- }
33231
- if (!callFunction) {
33232
- wasInvokedBeforeUsage = usageFunction !== null;
33233
- return;
33234
- }
33235
- wasInvokedBeforeUsage = isFunctionInvokedBeforeUsage(callFunction, usageNode, usageBoundary, scopes, new Set(visitedSymbolIds), new Set(visitedFunctionNodes));
33236
- });
33237
- return wasInvokedBeforeUsage;
33238
- };
33239
- const getMutationUsageBoundary = (usageNode, readNode) => {
33240
- const readStart = getRangeStart(readNode);
33241
- let readExpression = readNode;
33242
- let readParent = readExpression.parent;
33243
- while (readParent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(readParent.type) && "expression" in readParent && readParent.expression === readExpression) {
33244
- readExpression = readParent;
33245
- readParent = readExpression.parent;
33246
- }
33247
- if ((isNodeOfType(usageNode, "CallExpression") || isNodeOfType(usageNode, "NewExpression")) && usageNode.arguments?.some((argument) => argument === readExpression)) return usageNode.range?.[1] ?? null;
33248
- if (isAstDescendant(readNode, usageNode)) return readStart;
33249
- return getRangeStart(usageNode);
33250
- };
33251
- const wasMutatedBeforeUsage = (symbol, usageNode, readNode, scopes, visitedMutationSymbolIds = /* @__PURE__ */ new Set(), inheritedUsageBoundary, readNodesBySymbolId = /* @__PURE__ */ new Map()) => {
33252
- if (visitedMutationSymbolIds.has(symbol.id)) return false;
33253
- visitedMutationSymbolIds.add(symbol.id);
33254
- const usageBoundary = inheritedUsageBoundary === void 0 ? getMutationUsageBoundary(usageNode, readNode) : inheritedUsageBoundary;
33255
- if (typeof usageBoundary !== "number") return true;
33256
- const usageFunction = findEnclosingFunction(usageNode);
33257
- return symbol.references.some((reference) => {
33258
- const referenceStart = getRangeStart(reference.identifier);
33259
- const simpleAlias = getSimpleAlias(reference.identifier, scopes);
33260
- if (simpleAlias) return wasMutatedBeforeUsage(simpleAlias.symbol, usageNode, readNodesBySymbolId.get(simpleAlias.symbol.id) ?? simpleAlias.readNode, scopes, new Set(visitedMutationSymbolIds), usageBoundary, readNodesBySymbolId);
33261
- if (!isPotentialMutationReference(reference.identifier, readNode)) return false;
33262
- if (referenceStart === null) return true;
33263
- const mutationFunction = findEnclosingFunction(reference.identifier);
33264
- if (mutationFunction === usageFunction) return referenceStart < usageBoundary;
33265
- if (!mutationFunction) return usageFunction !== null;
33266
- if (usageFunction && isAstDescendant(usageFunction, mutationFunction)) return true;
33267
- return isFunctionInvokedBeforeUsage(mutationFunction, usageNode, usageBoundary, scopes, /* @__PURE__ */ new Set());
33268
- });
33269
- };
33270
- const getObjectPropertyProof = (objectExpression, propertyName, scopes, usageNode, visitedSymbolIds = /* @__PURE__ */ new Set(), inheritedUsageBoundary, readNodesBySymbolId = /* @__PURE__ */ new Map()) => {
33271
- if (!objectExpression) return ABSENT_PROPERTY_PROOF;
32775
+ const objectLiteralHasProperty = (objectExpression, propertyName) => {
32776
+ if (!objectExpression) return false;
33272
32777
  const unwrapped = stripParenExpression(objectExpression);
33273
- if (isNodeOfType(unwrapped, "Literal") || isStaticUndefined(unwrapped, scopes)) return ABSENT_PROPERTY_PROOF;
33274
- if (isNodeOfType(unwrapped, "Identifier")) {
33275
- const symbol = scopes.symbolFor(unwrapped);
33276
- const nextReadNodesBySymbolId = new Map(readNodesBySymbolId);
33277
- if (symbol) nextReadNodesBySymbolId.set(symbol.id, unwrapped);
33278
- const usageBoundary = inheritedUsageBoundary === void 0 ? getMutationUsageBoundary(usageNode, unwrapped) : inheritedUsageBoundary;
33279
- 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;
33280
- visitedSymbolIds.add(symbol.id);
33281
- return getObjectPropertyProof(symbol.initializer, propertyName, scopes, usageNode, visitedSymbolIds, usageBoundary, nextReadNodesBySymbolId);
33282
- }
33283
- if (isNodeOfType(unwrapped, "ConditionalExpression")) {
33284
- const consequent = getObjectPropertyProof(unwrapped.consequent, propertyName, scopes, usageNode, new Set(visitedSymbolIds), inheritedUsageBoundary, new Map(readNodesBySymbolId));
33285
- const alternate = getObjectPropertyProof(unwrapped.alternate, propertyName, scopes, usageNode, new Set(visitedSymbolIds), inheritedUsageBoundary, new Map(readNodesBySymbolId));
33286
- return consequent.status === alternate.status ? consequent : UNKNOWN_PROPERTY_PROOF;
33287
- }
33288
- 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);
33289
- if (!isNodeOfType(unwrapped, "ObjectExpression")) return UNKNOWN_PROPERTY_PROOF;
33290
- const properties = unwrapped.properties ?? [];
33291
- for (let propertyIndex = properties.length - 1; propertyIndex >= 0; propertyIndex -= 1) {
33292
- const property = properties[propertyIndex];
33293
- if (!property) continue;
33294
- if (isNodeOfType(property, "SpreadElement")) {
33295
- const spreadProof = getObjectPropertyProof(property.argument, propertyName, scopes, unwrapped, new Set(visitedSymbolIds), void 0);
33296
- if (spreadProof.status !== "absent") return spreadProof;
33297
- continue;
33298
- }
33299
- if (!isNodeOfType(property, "Property") || getStaticPropertyKeyName(property) !== propertyName) continue;
33300
- return isStaticUndefined(property.value, scopes) ? UNDEFINED_PROPERTY_PROOF : PRESENT_PROPERTY_PROOF;
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;
33301
32784
  }
33302
- return ABSENT_PROPERTY_PROOF;
32785
+ return false;
33303
32786
  };
33304
- const objectHasExplicitProperty = (objectExpression, propertyName, scopes, usageNode) => getObjectPropertyProof(objectExpression, propertyName, scopes, usageNode).status === "present";
33305
- const hasExplicitLocaleArgument = (argument, scopes) => {
32787
+ const hasExplicitLocaleArgument = (argument) => {
33306
32788
  if (!argument) return false;
33307
32789
  const unwrapped = stripParenExpression(argument);
33308
- if (isNodeOfType(unwrapped, "Identifier") && unwrapped.name === "undefined" && scopes.isGlobalReference(unwrapped) || isNodeOfType(unwrapped, "UnaryExpression") && unwrapped.operator === "void") return false;
32790
+ if (isNodeOfType(unwrapped, "Identifier") && unwrapped.name === "undefined") return false;
33309
32791
  return true;
33310
32792
  };
33311
- const isDeterministicLocaleMethodCall = (call, methodName, receiverIsProvablyDate, scopes) => {
32793
+ const isDeterministicLocaleMethodCall = (call, methodName, receiverIsProvablyDate) => {
33312
32794
  const localeArgument = call.arguments?.[0];
33313
- if (!hasExplicitLocaleArgument(localeArgument, scopes)) return false;
32795
+ if (!hasExplicitLocaleArgument(localeArgument)) return false;
33314
32796
  const optionsArgument = call.arguments?.[1];
33315
- if (objectHasExplicitProperty(optionsArgument, "timeZone", scopes, call)) return true;
32797
+ if (objectLiteralHasProperty(optionsArgument, "timeZone")) return true;
33316
32798
  return !DATE_ONLY_LOCALE_METHOD_NAMES.has(methodName) && !receiverIsProvablyDate;
33317
32799
  };
33318
- const matchLocaleMethodCall = (call, scopes) => {
32800
+ const matchLocaleMethodCall = (call) => {
33319
32801
  const callee = call.callee;
33320
32802
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
33321
32803
  if (!isNodeOfType(callee.property, "Identifier")) return null;
@@ -33323,7 +32805,7 @@ const matchLocaleMethodCall = (call, scopes) => {
33323
32805
  if (!LOCALE_FORMAT_METHOD_NAMES.has(methodName)) return null;
33324
32806
  const receiverIsProvablyDate = isProvableDateExpression(callee.object);
33325
32807
  if (methodName === "toLocaleString" && !receiverIsProvablyDate && !receiverNameLooksDateFlavored(callee.object)) return null;
33326
- if (isDeterministicLocaleMethodCall(call, methodName, receiverIsProvablyDate, scopes)) return null;
32808
+ if (isDeterministicLocaleMethodCall(call, methodName, receiverIsProvablyDate)) return null;
33327
32809
  return {
33328
32810
  node: call,
33329
32811
  display: `${methodName}()`
@@ -33339,13 +32821,13 @@ const getIntlFormatterName = (expression) => {
33339
32821
  if (!isNodeOfType(callee.property, "Identifier")) return null;
33340
32822
  return INTL_FORMATTER_NAMES.has(callee.property.name) ? callee.property.name : null;
33341
32823
  };
33342
- const isDeterministicIntlConstruction = (construction, formatterName, scopes) => {
32824
+ const isDeterministicIntlConstruction = (construction, formatterName) => {
33343
32825
  if (!isNodeOfType(construction, "CallExpression") && !isNodeOfType(construction, "NewExpression")) return false;
33344
- if (!hasExplicitLocaleArgument(construction.arguments?.[0], scopes)) return false;
32826
+ if (!hasExplicitLocaleArgument(construction.arguments?.[0])) return false;
33345
32827
  if (formatterName !== "DateTimeFormat") return true;
33346
- return objectHasExplicitProperty(construction.arguments?.[1], "timeZone", scopes, construction);
32828
+ return objectLiteralHasProperty(construction.arguments?.[1], "timeZone");
33347
32829
  };
33348
- const matchIntlFormatCall = (call, scopes) => {
32830
+ const matchIntlFormatCall = (call) => {
33349
32831
  const callee = call.callee;
33350
32832
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
33351
32833
  if (!isNodeOfType(callee.property, "Identifier")) return null;
@@ -33358,7 +32840,7 @@ const matchIntlFormatCall = (call, scopes) => {
33358
32840
  if (!construction) return null;
33359
32841
  const formatterName = getIntlFormatterName(construction);
33360
32842
  if (!formatterName) return null;
33361
- if (isDeterministicIntlConstruction(construction, formatterName, scopes)) return null;
32843
+ if (isDeterministicIntlConstruction(construction, formatterName)) return null;
33362
32844
  return {
33363
32845
  node: call,
33364
32846
  display: `Intl.${formatterName}().${callee.property.name}()`
@@ -33427,7 +32909,7 @@ const noLocaleFormatInRender = defineRule({
33427
32909
  fileIsEmailTemplate = hasEmailTemplateImport(node);
33428
32910
  },
33429
32911
  CallExpression(node) {
33430
- const match = matchLocaleMethodCall(node, context.scopes) ?? matchIntlFormatCall(node, context.scopes) ?? matchDateDefaultStringification(node);
32912
+ const match = matchLocaleMethodCall(node) ?? matchIntlFormatCall(node) ?? matchDateDefaultStringification(node);
33431
32913
  if (match) reportIfRenderPhase(match);
33432
32914
  },
33433
32915
  TemplateLiteral(node) {
@@ -33447,7 +32929,7 @@ const noLocaleFormatInRender = defineRule({
33447
32929
  walkAst(helperNode.body ?? helperNode, (child) => {
33448
32930
  if (isFunctionLike$1(child)) return false;
33449
32931
  if (!isNodeOfType(child, "CallExpression")) return;
33450
- const match = matchLocaleMethodCall(child, context.scopes) ?? matchIntlFormatCall(child, context.scopes);
32932
+ const match = matchLocaleMethodCall(child) ?? matchIntlFormatCall(child);
33451
32933
  if (!match || reportedNodes.has(match.node)) return;
33452
32934
  if (fileIsEmailTemplate) return;
33453
32935
  if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
@@ -37273,85 +36755,28 @@ const noRedundantShouldComponentUpdate = defineRule({
37273
36755
  });
37274
36756
  //#endregion
37275
36757
  //#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
37276
- const REPEATED_ANCESTOR_TYPES = new Set([
37277
- "DoWhileStatement",
37278
- "ForInStatement",
37279
- "ForOfStatement",
37280
- "ForStatement",
37281
- "WhileStatement"
37282
- ]);
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
+ };
37283
36767
  const isSameRefCurrentMember = (node, refSymbol, scopes) => {
37284
- if (!isNodeOfType(node, "MemberExpression") || getStaticPropertyName(node) !== "current") return false;
36768
+ if (!isNodeOfType(node, "MemberExpression") || node.computed || !isNodeOfType(node.property, "Identifier") || node.property.name !== "current") return false;
37285
36769
  const receiver = stripParenExpression(node.object);
37286
36770
  return isNodeOfType(receiver, "Identifier") && resolveConstIdentifierAlias(receiver, scopes)?.id === refSymbol.id;
37287
36771
  };
37288
- const isSameRefCurrentAlias = (node, refSymbol, scopes) => {
37289
- if (isSameRefCurrentMember(node, refSymbol, scopes)) return true;
37290
- if (!isNodeOfType(node, "Identifier")) return false;
37291
- const aliasSymbol = scopes.symbolFor(node);
37292
- return aliasSymbol?.kind === "const" && aliasSymbol.initializer !== null && isSameRefCurrentMember(stripParenExpression(aliasSymbol.initializer), refSymbol, scopes);
37293
- };
37294
- const isEmptySentinel = (node, scopes) => isNodeOfType(node, "Literal") && node.value === null || isNodeOfType(node, "Identifier") && node.name === "undefined" && scopes.isGlobalReference(node);
37295
- const hasRepeatedExecutionAncestor = (node, stop) => {
37296
- let ancestor = node.parent;
37297
- while (ancestor && ancestor !== stop) {
37298
- if (isFunctionLike$1(ancestor) || REPEATED_ANCESTOR_TYPES.has(ancestor.type)) return true;
37299
- ancestor = ancestor.parent;
37300
- }
37301
- return ancestor !== stop;
37302
- };
37303
- const getBranchConstraints = (node, branchRoot) => {
37304
- const constraints = /* @__PURE__ */ new Map();
37305
- let descendant = node;
37306
- let ancestor = descendant.parent;
37307
- while (ancestor && descendant !== branchRoot) {
37308
- if (isNodeOfType(ancestor, "IfStatement")) {
37309
- if (ancestor.consequent === descendant) constraints.set(ancestor, true);
37310
- if (ancestor.alternate === descendant) constraints.set(ancestor, false);
37311
- }
37312
- descendant = ancestor;
37313
- ancestor = ancestor.parent;
37314
- }
37315
- return constraints;
37316
- };
37317
- const canExecuteTogether = (firstConstraints, secondConstraints) => {
37318
- for (const [statement, branch] of firstConstraints) {
37319
- const otherBranch = secondConstraints.get(statement);
37320
- if (otherBranch !== void 0 && otherBranch !== branch) return false;
37321
- }
37322
- return true;
37323
- };
37324
- const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol, scopes) => {
37325
- const assignmentConstraints = getBranchConstraints(assignmentExpression, branchRoot);
37326
- const assignmentStart = getRangeStart(assignmentExpression);
37327
- let hasCoExecutableWrite = false;
37328
- walkAst(branchRoot, (child) => {
37329
- if (hasCoExecutableWrite) return false;
37330
- const childStart = getRangeStart(child);
37331
- if (child === assignmentExpression || !isNodeOfType(child, "AssignmentExpression") || assignmentStart === null || childStart === null || childStart >= assignmentStart || resolveReactRefSymbol(child.left, scopes)?.id !== refSymbol.id || hasRepeatedExecutionAncestor(child, branchRoot)) return;
37332
- if (canExecuteTogether(assignmentConstraints, getBranchConstraints(child, branchRoot))) {
37333
- hasCoExecutableWrite = true;
37334
- return false;
37335
- }
37336
- });
37337
- return !hasCoExecutableWrite;
37338
- };
37339
36772
  const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes) => {
37340
- if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return true;
37341
- if (assignmentExpression.operator !== "=") return false;
36773
+ if (assignmentExpression.operator !== "=" || !isNodeOfType(assignmentExpression.right, "NewExpression")) return false;
37342
36774
  let descendant = assignmentExpression;
37343
36775
  let ancestor = descendant.parent;
37344
36776
  while (ancestor) {
37345
- if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(ancestor.test, "BinaryExpression") && [
37346
- "===",
37347
- "==",
37348
- "!==",
37349
- "!="
37350
- ].includes(ancestor.test.operator)) {
36777
+ if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant && isNodeOfType(ancestor.test, "BinaryExpression") && (ancestor.test.operator === "===" || ancestor.test.operator === "==")) {
37351
36778
  const { left, right } = ancestor.test;
37352
- const comparesEmptySentinel = isSameRefCurrentAlias(left, refSymbol, scopes) && isEmptySentinel(right, scopes) || isSameRefCurrentAlias(right, refSymbol, scopes) && isEmptySentinel(left, scopes);
37353
- const guardedBranch = ancestor.test.operator === "===" || ancestor.test.operator === "==" ? ancestor.consequent : ancestor.alternate;
37354
- if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch && !hasRepeatedExecutionAncestor(assignmentExpression, guardedBranch) && hasNoPriorCoExecutableWrite(assignmentExpression, guardedBranch, refSymbol, scopes)) return true;
36779
+ if (isSameRefCurrentMember(left, refSymbol, scopes) && isNodeOfType(right, "Literal") && right.value === null || isSameRefCurrentMember(right, refSymbol, scopes) && isNodeOfType(left, "Literal") && left.value === null) return true;
37355
36780
  }
37356
36781
  descendant = ancestor;
37357
36782
  ancestor = descendant.parent;
@@ -44260,13 +43685,13 @@ const resolveTanstackQueryHookName = (callExpression) => {
44260
43685
  }
44261
43686
  return null;
44262
43687
  };
44263
- const resolveHookNameFromInitializer = (initializer, scopes) => {
43688
+ const resolveHookNameFromInitializer = (initializer) => {
44264
43689
  if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
44265
43690
  if (!isNodeOfType(initializer, "Identifier")) return null;
44266
- const resolvedSymbol = resolveConstIdentifierAlias(initializer, scopes);
44267
- if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
44268
- if (!isNodeOfType(resolvedSymbol.initializer, "CallExpression")) return null;
44269
- return resolveTanstackQueryHookName(resolvedSymbol.initializer);
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);
44270
43695
  };
44271
43696
  const queryNoRestDestructuring = defineRule({
44272
43697
  id: "query-no-rest-destructuring",
@@ -44279,7 +43704,7 @@ const queryNoRestDestructuring = defineRule({
44279
43704
  if (!isNodeOfType(node.id, "ObjectPattern")) return;
44280
43705
  if (!node.init) return;
44281
43706
  if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
44282
- const hookName = resolveHookNameFromInitializer(node.init, context.scopes);
43707
+ const hookName = resolveHookNameFromInitializer(node.init);
44283
43708
  if (!hookName) return;
44284
43709
  context.report({
44285
43710
  node: node.id,
@@ -45634,7 +45059,7 @@ const DEFERRABLE_HOOK_NAMES = new Set([
45634
45059
  "useParams",
45635
45060
  "usePathname"
45636
45061
  ]);
45637
- const findHookCallBindings = (componentBody, scopes) => {
45062
+ const findHookCallBindings = (componentBody) => {
45638
45063
  const bindings = [];
45639
45064
  if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
45640
45065
  for (const statement of componentBody.body ?? []) {
@@ -45645,10 +45070,8 @@ const findHookCallBindings = (componentBody, scopes) => {
45645
45070
  const callee = declarator.init.callee;
45646
45071
  if (!isNodeOfType(callee, "Identifier")) continue;
45647
45072
  if (!DEFERRABLE_HOOK_NAMES.has(callee.name)) continue;
45648
- const valueSymbol = scopes.symbolFor(declarator.id);
45649
- if (!valueSymbol) continue;
45650
45073
  bindings.push({
45651
- valueSymbol,
45074
+ valueName: declarator.id.name,
45652
45075
  hookName: callee.name,
45653
45076
  declarator
45654
45077
  });
@@ -45656,49 +45079,6 @@ const findHookCallBindings = (componentBody, scopes) => {
45656
45079
  }
45657
45080
  return bindings;
45658
45081
  };
45659
- const collectExactAliasSymbols = (componentBody, sourceSymbol, scopes) => {
45660
- const symbols = [sourceSymbol];
45661
- const symbolIds = new Set([sourceSymbol.id]);
45662
- const aliasSourceIdentifiers = /* @__PURE__ */ new Set();
45663
- if (!isNodeOfType(componentBody, "BlockStatement")) return {
45664
- symbols,
45665
- aliasSourceIdentifiers
45666
- };
45667
- let didFindAlias = true;
45668
- while (didFindAlias) {
45669
- didFindAlias = false;
45670
- for (const statement of componentBody.body ?? []) {
45671
- if (!isNodeOfType(statement, "VariableDeclaration") || statement.kind !== "const") continue;
45672
- for (const declarator of statement.declarations ?? []) {
45673
- let aliasIdentifier = null;
45674
- let sourceIdentifier = null;
45675
- const initializer = declarator.init ? stripParenExpression(declarator.init) : null;
45676
- const arrayBinding = isNodeOfType(declarator.id, "ArrayPattern") ? declarator.id.elements[0] : null;
45677
- const arrayValueNode = isNodeOfType(initializer, "ArrayExpression") ? initializer.elements[0] : null;
45678
- const arrayValue = arrayValueNode && !isNodeOfType(arrayValueNode, "SpreadElement") ? stripParenExpression(arrayValueNode) : null;
45679
- if (isNodeOfType(declarator.id, "Identifier") && isNodeOfType(initializer, "Identifier")) {
45680
- aliasIdentifier = declarator.id;
45681
- sourceIdentifier = initializer;
45682
- } else if (isNodeOfType(declarator.id, "ArrayPattern") && declarator.id.elements.length === 1 && isNodeOfType(arrayBinding, "Identifier") && isNodeOfType(initializer, "ArrayExpression") && initializer.elements.length === 1 && isNodeOfType(arrayValue, "Identifier")) {
45683
- aliasIdentifier = arrayBinding;
45684
- sourceIdentifier = arrayValue;
45685
- }
45686
- if (!aliasIdentifier || !sourceIdentifier) continue;
45687
- const referencedSymbol = scopes.symbolFor(sourceIdentifier);
45688
- const aliasSymbol = scopes.symbolFor(aliasIdentifier);
45689
- if (!referencedSymbol || !symbolIds.has(referencedSymbol.id) || !aliasSymbol || aliasSymbol.kind !== "const" || symbolIds.has(aliasSymbol.id)) continue;
45690
- symbolIds.add(aliasSymbol.id);
45691
- symbols.push(aliasSymbol);
45692
- aliasSourceIdentifiers.add(sourceIdentifier);
45693
- didFindAlias = true;
45694
- }
45695
- }
45696
- }
45697
- return {
45698
- symbols,
45699
- aliasSourceIdentifiers
45700
- };
45701
- };
45702
45082
  const rerenderDeferReadsHook = defineRule({
45703
45083
  id: "rerender-defer-reads-hook",
45704
45084
  title: "URL hook value only read in handlers",
@@ -45709,13 +45089,15 @@ const rerenderDeferReadsHook = defineRule({
45709
45089
  create: (context) => {
45710
45090
  const checkComponent = (componentBody) => {
45711
45091
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
45712
- const bindings = findHookCallBindings(componentBody, context.scopes);
45092
+ const bindings = findHookCallBindings(componentBody);
45713
45093
  if (bindings.length === 0) return;
45714
45094
  const handlerBindingNames = collectHandlerBindingNames(componentBody);
45715
45095
  for (const binding of bindings) {
45716
- const { symbols, aliasSourceIdentifiers } = collectExactAliasSymbols(componentBody, binding.valueSymbol, context.scopes);
45717
45096
  const referenceLocations = [];
45718
- for (const symbol of symbols) for (const reference of symbol.references) if (!aliasSourceIdentifiers.has(reference.identifier)) referenceLocations.push(reference.identifier);
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
+ });
45719
45101
  if (referenceLocations.length === 0) continue;
45720
45102
  if (!referenceLocations.every((ref) => isInsideEventHandler(ref, handlerBindingNames))) continue;
45721
45103
  context.report({
@@ -53664,8 +53046,7 @@ const serverCacheWithObjectLiteral = defineRule({
53664
53046
  if (!isNodeOfType(node.callee, "Identifier")) return;
53665
53047
  if (!cachedFunctionNames.has(node.callee.name)) return;
53666
53048
  const firstArg = node.arguments?.[0];
53667
- if (!firstArg || isNodeOfType(firstArg, "SpreadElement")) return;
53668
- if (!isNodeOfType(unwrapObjectIntegrityExpression(firstArg, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES), "ObjectExpression")) return;
53049
+ if (!isNodeOfType(firstArg, "ObjectExpression")) return;
53669
53050
  context.report({
53670
53051
  node,
53671
53052
  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.47beb25",
3
+ "version": "0.7.6-dev.4fb3694",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",