oxlint-plugin-react-doctor 0.7.6-dev.c5a06bd → 0.7.6-dev.c6f996e

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 +75 -546
  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;
@@ -20535,24 +20533,8 @@ const hasDirective = (programNode, directive) => {
20535
20533
  return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
20536
20534
  };
20537
20535
  //#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
- };
20536
+ //#region src/plugin/utils/is-component-assignment.ts
20537
+ 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
20538
  //#endregion
20557
20539
  //#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
20558
20540
  const nextjsAsyncClientComponent = defineRule({
@@ -20578,10 +20560,10 @@ const nextjsAsyncClientComponent = defineRule({
20578
20560
  },
20579
20561
  VariableDeclarator(node) {
20580
20562
  if (!fileHasUseClient) return;
20563
+ if (!isComponentAssignment(node)) return;
20564
+ if (!isInlineFunctionExpression(node.init)) return;
20565
+ if (!node.init.async) return;
20581
20566
  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
20567
  context.report({
20586
20568
  node,
20587
20569
  message: `Async client component "${node.id.name}" fails to render because client components can't be async.`
@@ -26317,9 +26299,6 @@ const functionContainsReactRenderOutput = (functionNode, scopes) => {
26317
26299
  return hasRenderOutput;
26318
26300
  };
26319
26301
  //#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
26323
26302
  //#region src/plugin/utils/is-component-declaration.ts
26324
26303
  const isComponentDeclaration = (node) => isNodeOfType(node, "FunctionDeclaration") && node.id !== null && Boolean(node.id?.name) && isUppercaseName(node.id.name);
26325
26304
  //#endregion
@@ -31961,26 +31940,6 @@ const noImgLazyWithHighFetchpriority = defineRule({
31961
31940
  } })
31962
31941
  });
31963
31942
  //#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
31984
31943
  //#region src/plugin/rules/state-and-effects/no-impure-state-updater.ts
31985
31944
  const TIMER_FUNCTION_NAMES = new Set([
31986
31945
  "cancelAnimationFrame",
@@ -32012,33 +31971,6 @@ const NOTIFICATION_METHOD_NAMES = new Set([
32012
31971
  "success",
32013
31972
  "warning"
32014
31973
  ]);
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
- };
32042
31974
  const getMemberCall = (node) => {
32043
31975
  if (!isNodeOfType(node, "CallExpression")) return null;
32044
31976
  if (!isNodeOfType(node.callee, "MemberExpression") || node.callee.computed) return null;
@@ -32050,37 +31982,27 @@ const getMemberCall = (node) => {
32050
31982
  };
32051
31983
  const isNotificationReceiver = (receiver, scopes) => {
32052
31984
  if (!isNodeOfType(receiver, "Identifier")) 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));
31985
+ if (!NOTIFICATION_RECEIVER_NAMES.has(receiver.name)) return false;
32055
31986
  const symbol = scopes.symbolFor(receiver);
31987
+ if (symbol?.kind === "import") return true;
32056
31988
  if (!isNodeOfType(symbol?.initializer, "CallExpression")) return false;
32057
31989
  const callee = symbol.initializer.callee;
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;
31990
+ return isNodeOfType(callee, "Identifier") && /^use(?:Message|Notification|Toast)$/.test(callee.name);
32068
31991
  };
32069
31992
  const getKnownImpureCall = (callExpression, scopes) => {
32070
31993
  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}()`;
32072
31994
  const memberCall = getMemberCall(callExpression);
32073
31995
  if (!memberCall) return null;
32074
31996
  const { methodName, receiver } = memberCall;
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}()`;
31997
+ if (STORAGE_MUTATION_METHOD_NAMES.has(methodName) && isNodeOfType(receiver, "Identifier") && STORAGE_RECEIVER_NAMES.has(receiver.name) && scopes.isGlobalReference(receiver)) return `${receiver.name}.${methodName}()`;
31998
+ if (EXTERNAL_READ_METHOD_NAMES.has(methodName)) return `.${methodName}()`;
31999
+ if (NOTIFICATION_METHOD_NAMES.has(methodName) && isNodeOfType(receiver, "Identifier") && isNotificationReceiver(receiver, scopes)) return `${receiver.name}.${methodName}()`;
32080
32000
  return null;
32081
32001
  };
32082
32002
  const getExternalAssignmentDescription = (assignmentTarget, updater, scopes) => {
32083
- const rootIdentifier = getRootIdentifier$1(assignmentTarget);
32003
+ let rootIdentifier = null;
32004
+ if (isNodeOfType(assignmentTarget, "Identifier")) rootIdentifier = assignmentTarget;
32005
+ else if (isNodeOfType(assignmentTarget, "MemberExpression") && isNodeOfType(assignmentTarget.object, "Identifier")) rootIdentifier = assignmentTarget.object;
32084
32006
  if (!rootIdentifier) return null;
32085
32007
  const updaterScope = scopes.ownScopeFor(updater);
32086
32008
  if (!updaterScope) return null;
@@ -32089,26 +32011,12 @@ const getExternalAssignmentDescription = (assignmentTarget, updater, scopes) =>
32089
32011
  if (symbol.kind === "parameter" && symbol.scope === updaterScope) return `the updater argument "${rootIdentifier.name}"`;
32090
32012
  return isDescendantScope(symbol.scope, updaterScope) ? null : `the captured value "${rootIdentifier.name}"`;
32091
32013
  };
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
- };
32106
32014
  const findImpureUpdaterOperation = (updater, scopes) => {
32107
32015
  const analysis = getProgramAnalysis(updater);
32108
32016
  let operation = null;
32109
32017
  walkAst(updater, (child) => {
32110
32018
  if (operation) return false;
32111
- if (child !== updater && isFunctionLike$1(child) && !isDefinitelySynchronousCallback(child, scopes)) return false;
32019
+ if (child !== updater && isFunctionLike$1(child) && !executesDuringRender(child, scopes)) return false;
32112
32020
  if (isNodeOfType(child, "CallExpression")) {
32113
32021
  if (isNodeOfType(child.callee, "Identifier") && analysis) {
32114
32022
  const calleeReference = getRef(analysis, child.callee);
@@ -32140,25 +32048,18 @@ const noImpureStateUpdater = defineRule({
32140
32048
  severity: "error",
32141
32049
  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.",
32142
32050
  create: (context) => ({ CallExpression(node) {
32143
- const updaterArgument = node.arguments?.[0];
32144
- if (!updaterArgument) return;
32051
+ const updater = node.arguments?.[0];
32052
+ if (!updater || !isFunctionLike$1(updater)) return;
32145
32053
  const analysis = getProgramAnalysis(node);
32146
32054
  if (!analysis || !isNodeOfType(node.callee, "Identifier")) return;
32147
32055
  const calleeReference = getRef(analysis, node.callee);
32148
32056
  if (!calleeReference || !isStateSetterCall(analysis, calleeReference)) return;
32149
32057
  const stateDeclarator = getUseStateDecl(analysis, calleeReference);
32150
32058
  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;
32158
32059
  const operation = findImpureUpdaterOperation(updater, context.scopes);
32159
32060
  if (!operation) return;
32160
32061
  context.report({
32161
- node: updaterArgument,
32062
+ node: updater,
32162
32063
  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.`
32163
32064
  });
32164
32065
  } })
@@ -32447,12 +32348,11 @@ const isMemoCall = (node) => {
32447
32348
  };
32448
32349
  const isDefaultEquivalentComparator = (comparator) => isNodeOfType(comparator, "Identifier") && (comparator.name === "undefined" || comparator.name === "shallowEqual");
32449
32350
  const hasCustomComparator = (node) => isNodeOfType(node, "CallExpression") && (node.arguments?.length ?? 0) >= 2 && !isDefaultEquivalentComparator(node.arguments?.[1]);
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";
32351
+ const isInlineReference = (node) => {
32352
+ 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";
32353
+ if (isNodeOfType(node, "ObjectExpression")) return "objects";
32354
+ if (isNodeOfType(node, "ArrayExpression")) return "Arrays";
32355
+ if (isNodeOfType(node, "JSXElement") || isNodeOfType(node, "JSXFragment")) return "JSX";
32456
32356
  return null;
32457
32357
  };
32458
32358
  const noInlinePropOnMemoComponent = defineRule({
@@ -32482,7 +32382,7 @@ const noInlinePropOnMemoComponent = defineRule({
32482
32382
  let elementName = null;
32483
32383
  if (isNodeOfType(openingElement.name, "JSXIdentifier")) elementName = openingElement.name.name;
32484
32384
  if (!elementName || !memoizedComponentNames.has(elementName)) return;
32485
- const propType = isInlineReference(node.value.expression, context.scopes);
32385
+ const propType = isInlineReference(node.value.expression);
32486
32386
  if (propType) context.report({
32487
32387
  node: node.value.expression,
32488
32388
  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`
@@ -33003,18 +32903,6 @@ const INTL_FORMAT_METHOD_NAMES = new Set([
33003
32903
  "formatToParts",
33004
32904
  "formatRange"
33005
32905
  ]);
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
- ]);
33018
32906
  const isProvableDateExpression = (expression) => {
33019
32907
  if (!expression) return false;
33020
32908
  const unwrapped = stripParenExpression(expression);
@@ -33029,290 +32917,32 @@ const receiverNameLooksDateFlavored = (expression) => {
33029
32917
  if (isNodeOfType(unwrapped, "CallExpression")) return receiverNameLooksDateFlavored(unwrapped.callee);
33030
32918
  return false;
33031
32919
  };
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);
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;
33097
- return false;
33098
- };
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;
32920
+ const objectLiteralHasProperty = (objectExpression, propertyName) => {
32921
+ if (!objectExpression) return false;
33269
32922
  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;
32923
+ if (!isNodeOfType(unwrapped, "ObjectExpression")) return false;
32924
+ for (const property of unwrapped.properties ?? []) {
32925
+ if (!isNodeOfType(property, "Property")) continue;
32926
+ if (property.computed) continue;
32927
+ if (isNodeOfType(property.key, "Identifier") && property.key.name === propertyName) return true;
32928
+ if (isNodeOfType(property.key, "Literal") && property.key.value === propertyName) return true;
33298
32929
  }
33299
- return ABSENT_PROPERTY_PROOF;
32930
+ return false;
33300
32931
  };
33301
- const objectHasExplicitProperty = (objectExpression, propertyName, scopes, usageNode) => getObjectPropertyProof(objectExpression, propertyName, scopes, usageNode).status === "present";
33302
- const hasExplicitLocaleArgument = (argument, scopes) => {
32932
+ const hasExplicitLocaleArgument = (argument) => {
33303
32933
  if (!argument) return false;
33304
32934
  const unwrapped = stripParenExpression(argument);
33305
- if (isNodeOfType(unwrapped, "Identifier") && unwrapped.name === "undefined" && scopes.isGlobalReference(unwrapped) || isNodeOfType(unwrapped, "UnaryExpression") && unwrapped.operator === "void") return false;
32935
+ if (isNodeOfType(unwrapped, "Identifier") && unwrapped.name === "undefined") return false;
33306
32936
  return true;
33307
32937
  };
33308
- const isDeterministicLocaleMethodCall = (call, methodName, receiverIsProvablyDate, scopes) => {
32938
+ const isDeterministicLocaleMethodCall = (call, methodName, receiverIsProvablyDate) => {
33309
32939
  const localeArgument = call.arguments?.[0];
33310
- if (!hasExplicitLocaleArgument(localeArgument, scopes)) return false;
32940
+ if (!hasExplicitLocaleArgument(localeArgument)) return false;
33311
32941
  const optionsArgument = call.arguments?.[1];
33312
- if (objectHasExplicitProperty(optionsArgument, "timeZone", scopes, call)) return true;
32942
+ if (objectLiteralHasProperty(optionsArgument, "timeZone")) return true;
33313
32943
  return !DATE_ONLY_LOCALE_METHOD_NAMES.has(methodName) && !receiverIsProvablyDate;
33314
32944
  };
33315
- const matchLocaleMethodCall = (call, scopes) => {
32945
+ const matchLocaleMethodCall = (call) => {
33316
32946
  const callee = call.callee;
33317
32947
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
33318
32948
  if (!isNodeOfType(callee.property, "Identifier")) return null;
@@ -33320,7 +32950,7 @@ const matchLocaleMethodCall = (call, scopes) => {
33320
32950
  if (!LOCALE_FORMAT_METHOD_NAMES.has(methodName)) return null;
33321
32951
  const receiverIsProvablyDate = isProvableDateExpression(callee.object);
33322
32952
  if (methodName === "toLocaleString" && !receiverIsProvablyDate && !receiverNameLooksDateFlavored(callee.object)) return null;
33323
- if (isDeterministicLocaleMethodCall(call, methodName, receiverIsProvablyDate, scopes)) return null;
32953
+ if (isDeterministicLocaleMethodCall(call, methodName, receiverIsProvablyDate)) return null;
33324
32954
  return {
33325
32955
  node: call,
33326
32956
  display: `${methodName}()`
@@ -33336,13 +32966,13 @@ const getIntlFormatterName = (expression) => {
33336
32966
  if (!isNodeOfType(callee.property, "Identifier")) return null;
33337
32967
  return INTL_FORMATTER_NAMES.has(callee.property.name) ? callee.property.name : null;
33338
32968
  };
33339
- const isDeterministicIntlConstruction = (construction, formatterName, scopes) => {
32969
+ const isDeterministicIntlConstruction = (construction, formatterName) => {
33340
32970
  if (!isNodeOfType(construction, "CallExpression") && !isNodeOfType(construction, "NewExpression")) return false;
33341
- if (!hasExplicitLocaleArgument(construction.arguments?.[0], scopes)) return false;
32971
+ if (!hasExplicitLocaleArgument(construction.arguments?.[0])) return false;
33342
32972
  if (formatterName !== "DateTimeFormat") return true;
33343
- return objectHasExplicitProperty(construction.arguments?.[1], "timeZone", scopes, construction);
32973
+ return objectLiteralHasProperty(construction.arguments?.[1], "timeZone");
33344
32974
  };
33345
- const matchIntlFormatCall = (call, scopes) => {
32975
+ const matchIntlFormatCall = (call) => {
33346
32976
  const callee = call.callee;
33347
32977
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
33348
32978
  if (!isNodeOfType(callee.property, "Identifier")) return null;
@@ -33355,7 +32985,7 @@ const matchIntlFormatCall = (call, scopes) => {
33355
32985
  if (!construction) return null;
33356
32986
  const formatterName = getIntlFormatterName(construction);
33357
32987
  if (!formatterName) return null;
33358
- if (isDeterministicIntlConstruction(construction, formatterName, scopes)) return null;
32988
+ if (isDeterministicIntlConstruction(construction, formatterName)) return null;
33359
32989
  return {
33360
32990
  node: call,
33361
32991
  display: `Intl.${formatterName}().${callee.property.name}()`
@@ -33424,7 +33054,7 @@ const noLocaleFormatInRender = defineRule({
33424
33054
  fileIsEmailTemplate = hasEmailTemplateImport(node);
33425
33055
  },
33426
33056
  CallExpression(node) {
33427
- const match = matchLocaleMethodCall(node, context.scopes) ?? matchIntlFormatCall(node, context.scopes) ?? matchDateDefaultStringification(node);
33057
+ const match = matchLocaleMethodCall(node) ?? matchIntlFormatCall(node) ?? matchDateDefaultStringification(node);
33428
33058
  if (match) reportIfRenderPhase(match);
33429
33059
  },
33430
33060
  TemplateLiteral(node) {
@@ -33444,7 +33074,7 @@ const noLocaleFormatInRender = defineRule({
33444
33074
  walkAst(helperNode.body ?? helperNode, (child) => {
33445
33075
  if (isFunctionLike$1(child)) return false;
33446
33076
  if (!isNodeOfType(child, "CallExpression")) return;
33447
- const match = matchLocaleMethodCall(child, context.scopes) ?? matchIntlFormatCall(child, context.scopes);
33077
+ const match = matchLocaleMethodCall(child) ?? matchIntlFormatCall(child);
33448
33078
  if (!match || reportedNodes.has(match.node)) return;
33449
33079
  if (fileIsEmailTemplate) return;
33450
33080
  if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
@@ -37270,85 +36900,28 @@ const noRedundantShouldComponentUpdate = defineRule({
37270
36900
  });
37271
36901
  //#endregion
37272
36902
  //#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
37273
- const REPEATED_ANCESTOR_TYPES = new Set([
37274
- "DoWhileStatement",
37275
- "ForInStatement",
37276
- "ForOfStatement",
37277
- "ForStatement",
37278
- "WhileStatement"
37279
- ]);
36903
+ const resolveReactRefSymbol = (memberExpression, scopes) => {
36904
+ const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
36905
+ if (!isNodeOfType(memberExpression, "MemberExpression") || memberExpression.computed || !isNodeOfType(memberExpression.property, "Identifier") || memberExpression.property.name !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
36906
+ const symbol = resolveConstIdentifierAlias(receiver, scopes);
36907
+ if (!symbol?.initializer) return null;
36908
+ const initializer = stripParenExpression(symbol.initializer);
36909
+ if (!isNodeOfType(initializer, "CallExpression")) return null;
36910
+ return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
36911
+ };
37280
36912
  const isSameRefCurrentMember = (node, refSymbol, scopes) => {
37281
- if (!isNodeOfType(node, "MemberExpression") || getStaticPropertyName(node) !== "current") return false;
36913
+ if (!isNodeOfType(node, "MemberExpression") || node.computed || !isNodeOfType(node.property, "Identifier") || node.property.name !== "current") return false;
37282
36914
  const receiver = stripParenExpression(node.object);
37283
36915
  return isNodeOfType(receiver, "Identifier") && resolveConstIdentifierAlias(receiver, scopes)?.id === refSymbol.id;
37284
36916
  };
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
- };
37336
36917
  const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes) => {
37337
- if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return true;
37338
- if (assignmentExpression.operator !== "=") return false;
36918
+ if (assignmentExpression.operator !== "=" || !isNodeOfType(assignmentExpression.right, "NewExpression")) return false;
37339
36919
  let descendant = assignmentExpression;
37340
36920
  let ancestor = descendant.parent;
37341
36921
  while (ancestor) {
37342
- if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(ancestor.test, "BinaryExpression") && [
37343
- "===",
37344
- "==",
37345
- "!==",
37346
- "!="
37347
- ].includes(ancestor.test.operator)) {
36922
+ if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant && isNodeOfType(ancestor.test, "BinaryExpression") && (ancestor.test.operator === "===" || ancestor.test.operator === "==")) {
37348
36923
  const { left, right } = ancestor.test;
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;
36924
+ if (isSameRefCurrentMember(left, refSymbol, scopes) && isNodeOfType(right, "Literal") && right.value === null || isSameRefCurrentMember(right, refSymbol, scopes) && isNodeOfType(left, "Literal") && left.value === null) return true;
37352
36925
  }
37353
36926
  descendant = ancestor;
37354
36927
  ancestor = descendant.parent;
@@ -45631,7 +45204,7 @@ const DEFERRABLE_HOOK_NAMES = new Set([
45631
45204
  "useParams",
45632
45205
  "usePathname"
45633
45206
  ]);
45634
- const findHookCallBindings = (componentBody, scopes) => {
45207
+ const findHookCallBindings = (componentBody) => {
45635
45208
  const bindings = [];
45636
45209
  if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
45637
45210
  for (const statement of componentBody.body ?? []) {
@@ -45642,10 +45215,8 @@ const findHookCallBindings = (componentBody, scopes) => {
45642
45215
  const callee = declarator.init.callee;
45643
45216
  if (!isNodeOfType(callee, "Identifier")) continue;
45644
45217
  if (!DEFERRABLE_HOOK_NAMES.has(callee.name)) continue;
45645
- const valueSymbol = scopes.symbolFor(declarator.id);
45646
- if (!valueSymbol) continue;
45647
45218
  bindings.push({
45648
- valueSymbol,
45219
+ valueName: declarator.id.name,
45649
45220
  hookName: callee.name,
45650
45221
  declarator
45651
45222
  });
@@ -45653,49 +45224,6 @@ const findHookCallBindings = (componentBody, scopes) => {
45653
45224
  }
45654
45225
  return bindings;
45655
45226
  };
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
- };
45699
45227
  const rerenderDeferReadsHook = defineRule({
45700
45228
  id: "rerender-defer-reads-hook",
45701
45229
  title: "URL hook value only read in handlers",
@@ -45706,13 +45234,15 @@ const rerenderDeferReadsHook = defineRule({
45706
45234
  create: (context) => {
45707
45235
  const checkComponent = (componentBody) => {
45708
45236
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
45709
- const bindings = findHookCallBindings(componentBody, context.scopes);
45237
+ const bindings = findHookCallBindings(componentBody);
45710
45238
  if (bindings.length === 0) return;
45711
45239
  const handlerBindingNames = collectHandlerBindingNames(componentBody);
45712
45240
  for (const binding of bindings) {
45713
- const { symbols, aliasSourceIdentifiers } = collectExactAliasSymbols(componentBody, binding.valueSymbol, context.scopes);
45714
45241
  const referenceLocations = [];
45715
- for (const symbol of symbols) for (const reference of symbol.references) if (!aliasSourceIdentifiers.has(reference.identifier)) referenceLocations.push(reference.identifier);
45242
+ walkAst(componentBody, (child) => {
45243
+ if (isNodeOfType(binding.declarator, "VariableDeclarator") && child === binding.declarator.id) return;
45244
+ if (isNodeOfType(child, "Identifier") && child.name === binding.valueName) referenceLocations.push(child);
45245
+ });
45716
45246
  if (referenceLocations.length === 0) continue;
45717
45247
  if (!referenceLocations.every((ref) => isInsideEventHandler(ref, handlerBindingNames))) continue;
45718
45248
  context.report({
@@ -53661,8 +53191,7 @@ const serverCacheWithObjectLiteral = defineRule({
53661
53191
  if (!isNodeOfType(node.callee, "Identifier")) return;
53662
53192
  if (!cachedFunctionNames.has(node.callee.name)) return;
53663
53193
  const firstArg = node.arguments?.[0];
53664
- if (!firstArg || isNodeOfType(firstArg, "SpreadElement")) return;
53665
- if (!isNodeOfType(unwrapObjectIntegrityExpression(firstArg, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES), "ObjectExpression")) return;
53194
+ if (!isNodeOfType(firstArg, "ObjectExpression")) return;
53666
53195
  context.report({
53667
53196
  node,
53668
53197
  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.c5a06bd",
3
+ "version": "0.7.6-dev.c6f996e",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",