oxlint-plugin-react-doctor 0.7.6-dev.e141d90 → 0.7.6-dev.eb09902

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 +45 -247
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6296,131 +6296,6 @@ const isGlobalMethodCall = (node, objectName, methodName) => {
6296
6296
  return isNodeOfType(receiver, "Identifier") && receiver.name === objectName && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
6297
6297
  };
6298
6298
  //#endregion
6299
- //#region src/plugin/utils/collect-safely-validated-local-storage-keys.ts
6300
- const isLocalStorageCall = (node, methodName) => {
6301
- if (!isNodeOfType(node, "CallExpression")) return false;
6302
- const callee = stripParenExpression(node.callee);
6303
- if (!isNodeOfType(callee, "MemberExpression")) return false;
6304
- const receiver = stripParenExpression(callee.object);
6305
- return isNodeOfType(receiver, "Identifier") && receiver.name === "localStorage" && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
6306
- };
6307
- const catchReturnsFallback = (tryStatement) => {
6308
- const handler = tryStatement.handler;
6309
- if (!handler) return false;
6310
- let hasReturn = false;
6311
- let hasThrow = false;
6312
- walkAst(handler.body, (child) => {
6313
- if (isFunctionLike$1(child)) return false;
6314
- if (isNodeOfType(child, "ReturnStatement")) hasReturn = true;
6315
- if (isNodeOfType(child, "ThrowStatement")) hasThrow = true;
6316
- });
6317
- return hasReturn && !hasThrow;
6318
- };
6319
- const resolveValidatorFunction = (callee, scopes) => {
6320
- const unwrappedCallee = stripParenExpression(callee);
6321
- if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
6322
- const symbol = scopes.symbolFor(unwrappedCallee);
6323
- if (!symbol) return null;
6324
- const candidate = symbol.kind === "function" ? symbol.declarationNode : symbol.initializer;
6325
- return isFunctionLike$1(candidate) ? candidate : null;
6326
- };
6327
- const validatorChecksPayloadProperties = (validatorFunction, scopes) => {
6328
- if (!isFunctionLike$1(validatorFunction)) return false;
6329
- const firstParameter = validatorFunction.params?.[0];
6330
- if (!firstParameter || !isNodeOfType(firstParameter, "Identifier")) return false;
6331
- const parameterSymbol = scopes.symbolFor(firstParameter);
6332
- if (!parameterSymbol) return false;
6333
- const payloadSymbolIds = new Set([parameterSymbol.id]);
6334
- let hasPropertyTypeCheck = false;
6335
- walkAst(validatorFunction.body, (child) => {
6336
- if (isFunctionLike$1(child)) return false;
6337
- if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier") && child.init) {
6338
- const initializer = stripParenExpression(child.init);
6339
- if (isNodeOfType(initializer, "Identifier")) {
6340
- const initializerSymbol = scopes.symbolFor(initializer);
6341
- if (initializerSymbol && payloadSymbolIds.has(initializerSymbol.id)) {
6342
- const aliasSymbol = scopes.symbolFor(child.id);
6343
- if (aliasSymbol) payloadSymbolIds.add(aliasSymbol.id);
6344
- }
6345
- }
6346
- }
6347
- if (!isNodeOfType(child, "UnaryExpression") || child.operator !== "typeof") return;
6348
- const checkedValue = stripParenExpression(child.argument);
6349
- if (!isNodeOfType(checkedValue, "MemberExpression")) return;
6350
- const receiver = stripParenExpression(checkedValue.object);
6351
- if (!isNodeOfType(receiver, "Identifier")) return;
6352
- const receiverSymbol = scopes.symbolFor(receiver);
6353
- if (receiverSymbol && payloadSymbolIds.has(receiverSymbol.id)) hasPropertyTypeCheck = true;
6354
- });
6355
- return hasPropertyTypeCheck;
6356
- };
6357
- const incrementCount = (counts, keyValue) => {
6358
- counts.set(keyValue, (counts.get(keyValue) ?? 0) + 1);
6359
- };
6360
- const isReturnedExpression = (expression) => {
6361
- const parent = expression.parent;
6362
- return Boolean(parent && isNodeOfType(parent, "ReturnStatement") && parent.argument === expression);
6363
- };
6364
- const collectSafelyValidatedLocalStorageKeys = ({ programNode, scopes, resolveKey }) => {
6365
- const readCountsByKey = /* @__PURE__ */ new Map();
6366
- const safeReadCountsByKey = /* @__PURE__ */ new Map();
6367
- const safeReadSymbolIds = /* @__PURE__ */ new Set();
6368
- walkAst(programNode, (child) => {
6369
- if (!isNodeOfType(child, "CallExpression") || !isLocalStorageCall(child, "getItem")) return;
6370
- const keyArgument = child.arguments?.[0];
6371
- if (!keyArgument) return;
6372
- const keyValue = resolveKey(keyArgument);
6373
- if (keyValue !== null) incrementCount(readCountsByKey, keyValue);
6374
- });
6375
- walkAst(programNode, (child) => {
6376
- if (!isNodeOfType(child, "TryStatement") || !catchReturnsFallback(child)) return;
6377
- const rawValueKeys = /* @__PURE__ */ new Map();
6378
- const parsedValueSources = /* @__PURE__ */ new Map();
6379
- walkAst(child.block, (tryChild) => {
6380
- if (isFunctionLike$1(tryChild)) return false;
6381
- if (isNodeOfType(tryChild, "VariableDeclarator") && isNodeOfType(tryChild.id, "Identifier") && tryChild.init) {
6382
- const initializer = stripParenExpression(tryChild.init);
6383
- if (isNodeOfType(initializer, "CallExpression") && isLocalStorageCall(initializer, "getItem")) {
6384
- const keyArgument = initializer.arguments?.[0];
6385
- const bindingSymbol = scopes.symbolFor(tryChild.id);
6386
- if (keyArgument && bindingSymbol) {
6387
- const keyValue = resolveKey(keyArgument);
6388
- if (keyValue !== null) rawValueKeys.set(bindingSymbol.id, keyValue);
6389
- }
6390
- }
6391
- if (isNodeOfType(initializer, "CallExpression") && isGlobalMethodCall(initializer, "JSON", "parse")) {
6392
- const rawValueArgument = initializer.arguments?.[0];
6393
- const parsedValueSymbol = scopes.symbolFor(tryChild.id);
6394
- if (rawValueArgument && isNodeOfType(rawValueArgument, "Identifier") && parsedValueSymbol) {
6395
- const rawValueSymbol = scopes.symbolFor(rawValueArgument);
6396
- if (rawValueSymbol && rawValueKeys.has(rawValueSymbol.id)) parsedValueSources.set(parsedValueSymbol.id, rawValueSymbol.id);
6397
- }
6398
- }
6399
- }
6400
- if (!isNodeOfType(tryChild, "ConditionalExpression") || !isReturnedExpression(tryChild)) return;
6401
- const test = stripParenExpression(tryChild.test);
6402
- if (!isNodeOfType(test, "CallExpression")) return;
6403
- const testedValue = test.arguments?.[0];
6404
- if (!testedValue || !isNodeOfType(testedValue, "Identifier")) return;
6405
- const parsedValueSymbol = scopes.symbolFor(testedValue);
6406
- if (!parsedValueSymbol) return;
6407
- const rawValueSymbolId = parsedValueSources.get(parsedValueSymbol.id);
6408
- if (rawValueSymbolId === void 0) return;
6409
- const returnedValue = stripParenExpression(tryChild.consequent);
6410
- if (!isNodeOfType(returnedValue, "Identifier") || scopes.symbolFor(returnedValue)?.id !== parsedValueSymbol.id) return;
6411
- const validatorFunction = resolveValidatorFunction(test.callee, scopes);
6412
- if (!validatorFunction || !validatorChecksPayloadProperties(validatorFunction, scopes)) return;
6413
- const keyValue = rawValueKeys.get(rawValueSymbolId);
6414
- if (keyValue === void 0 || safeReadSymbolIds.has(rawValueSymbolId)) return;
6415
- safeReadSymbolIds.add(rawValueSymbolId);
6416
- incrementCount(safeReadCountsByKey, keyValue);
6417
- });
6418
- });
6419
- const safeKeys = /* @__PURE__ */ new Set();
6420
- for (const [keyValue, readCount] of readCountsByKey) if (safeReadCountsByKey.get(keyValue) === readCount) safeKeys.add(keyValue);
6421
- return safeKeys;
6422
- };
6423
- //#endregion
6424
6299
  //#region src/plugin/rules/client/client-localstorage-no-version.ts
6425
6300
  const VERSIONED_KEY_PATTERN = /(?:[._:-]v\d+|@\d+|\bv\d+\b)/i;
6426
6301
  const CAMEL_CASE_VERSIONED_KEY_PATTERN = /[a-z]V\d+/;
@@ -6442,39 +6317,26 @@ const clientLocalstorageNoVersion = defineRule({
6442
6317
  severity: "warn",
6443
6318
  category: "Correctness",
6444
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.",
6445
- create: (context) => {
6446
- let safelyValidatedStorageKeys = /* @__PURE__ */ new Set();
6447
- return {
6448
- Program(node) {
6449
- safelyValidatedStorageKeys = collectSafelyValidatedLocalStorageKeys({
6450
- programNode: node,
6451
- scopes: context.scopes,
6452
- resolveKey: (keyNode) => resolveStringKey(keyNode, context)
6453
- });
6454
- },
6455
- CallExpression(node) {
6456
- if (!isNodeOfType(node.callee, "MemberExpression")) return;
6457
- const receiver = stripParenExpression(node.callee.object);
6458
- if (!isNodeOfType(receiver, "Identifier")) return;
6459
- if (receiver.name !== "localStorage") return;
6460
- if (!isNodeOfType(node.callee.property, "Identifier")) return;
6461
- if (node.callee.property.name !== "setItem") return;
6462
- const keyArg = node.arguments?.[0];
6463
- if (!keyArg) return;
6464
- const keyValue = resolveStringKey(keyArg, context);
6465
- if (keyValue === null) return;
6466
- if (isVersionedKey(keyValue)) return;
6467
- if (safelyValidatedStorageKeys.has(keyValue)) return;
6468
- const valueArg = node.arguments?.[1];
6469
- if (!valueArg) return;
6470
- if (!isJsonStringifyCall(valueArg)) return;
6471
- context.report({
6472
- node: keyArg,
6473
- 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").`
6474
- });
6475
- }
6476
- };
6477
- }
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
+ } })
6478
6340
  });
6479
6341
  //#endregion
6480
6342
  //#region src/plugin/rules/client/client-passive-event-listeners.ts
@@ -20533,24 +20395,8 @@ const hasDirective = (programNode, directive) => {
20533
20395
  return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
20534
20396
  };
20535
20397
  //#endregion
20536
- //#region src/plugin/utils/unwrap-object-integrity-expression.ts
20537
- const OBJECT_INTEGRITY_METHOD_NAMES = new Set([
20538
- "freeze",
20539
- "seal",
20540
- "preventExtensions"
20541
- ]);
20542
- const OBJECT_FREEZE_OR_SEAL_METHOD_NAMES = new Set(["freeze", "seal"]);
20543
- const unwrapObjectIntegrityExpression = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
20544
- let expression = stripParenExpression(node);
20545
- while (isNodeOfType(expression, "CallExpression")) {
20546
- const callee = stripParenExpression(expression.callee);
20547
- 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;
20548
- const wrappedExpression = expression.arguments[0];
20549
- if (!wrappedExpression || isNodeOfType(wrappedExpression, "SpreadElement")) break;
20550
- expression = stripParenExpression(wrappedExpression);
20551
- }
20552
- return expression;
20553
- };
20398
+ //#region src/plugin/utils/is-component-assignment.ts
20399
+ 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"));
20554
20400
  //#endregion
20555
20401
  //#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
20556
20402
  const nextjsAsyncClientComponent = defineRule({
@@ -20576,10 +20422,10 @@ const nextjsAsyncClientComponent = defineRule({
20576
20422
  },
20577
20423
  VariableDeclarator(node) {
20578
20424
  if (!fileHasUseClient) return;
20425
+ if (!isComponentAssignment(node)) return;
20426
+ if (!isInlineFunctionExpression(node.init)) return;
20427
+ if (!node.init.async) return;
20579
20428
  if (!isNodeOfType(node.id, "Identifier")) return;
20580
- if (!isUppercaseName(node.id.name) || !node.init) return;
20581
- const componentFunction = unwrapObjectIntegrityExpression(node.init, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES);
20582
- if (!isInlineFunctionExpression(componentFunction) || !componentFunction.async) return;
20583
20429
  context.report({
20584
20430
  node,
20585
20431
  message: `Async client component "${node.id.name}" fails to render because client components can't be async.`
@@ -26315,9 +26161,6 @@ const functionContainsReactRenderOutput = (functionNode, scopes) => {
26315
26161
  return hasRenderOutput;
26316
26162
  };
26317
26163
  //#endregion
26318
- //#region src/plugin/utils/is-component-assignment.ts
26319
- 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"));
26320
- //#endregion
26321
26164
  //#region src/plugin/utils/is-component-declaration.ts
26322
26165
  const isComponentDeclaration = (node) => isNodeOfType(node, "FunctionDeclaration") && node.id !== null && Boolean(node.id?.name) && isUppercaseName(node.id.name);
26323
26166
  //#endregion
@@ -32367,12 +32210,11 @@ const isMemoCall = (node) => {
32367
32210
  };
32368
32211
  const isDefaultEquivalentComparator = (comparator) => isNodeOfType(comparator, "Identifier") && (comparator.name === "undefined" || comparator.name === "shallowEqual");
32369
32212
  const hasCustomComparator = (node) => isNodeOfType(node, "CallExpression") && (node.arguments?.length ?? 0) >= 2 && !isDefaultEquivalentComparator(node.arguments?.[1]);
32370
- const isInlineReference = (node, scopes) => {
32371
- const referenceNode = unwrapObjectIntegrityExpression(node, scopes);
32372
- 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";
32373
- if (isNodeOfType(referenceNode, "ObjectExpression")) return "objects";
32374
- if (isNodeOfType(referenceNode, "ArrayExpression")) return "Arrays";
32375
- if (isNodeOfType(referenceNode, "JSXElement") || isNodeOfType(referenceNode, "JSXFragment")) return "JSX";
32213
+ const isInlineReference = (node) => {
32214
+ 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";
32215
+ if (isNodeOfType(node, "ObjectExpression")) return "objects";
32216
+ if (isNodeOfType(node, "ArrayExpression")) return "Arrays";
32217
+ if (isNodeOfType(node, "JSXElement") || isNodeOfType(node, "JSXFragment")) return "JSX";
32376
32218
  return null;
32377
32219
  };
32378
32220
  const noInlinePropOnMemoComponent = defineRule({
@@ -32402,7 +32244,7 @@ const noInlinePropOnMemoComponent = defineRule({
32402
32244
  let elementName = null;
32403
32245
  if (isNodeOfType(openingElement.name, "JSXIdentifier")) elementName = openingElement.name.name;
32404
32246
  if (!elementName || !memoizedComponentNames.has(elementName)) return;
32405
- const propType = isInlineReference(node.value.expression, context.scopes);
32247
+ const propType = isInlineReference(node.value.expression);
32406
32248
  if (propType) context.report({
32407
32249
  node: node.value.expression,
32408
32250
  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`
@@ -43850,13 +43692,13 @@ const resolveTanstackQueryHookName = (callExpression) => {
43850
43692
  }
43851
43693
  return null;
43852
43694
  };
43853
- const resolveHookNameFromInitializer = (initializer, scopes) => {
43695
+ const resolveHookNameFromInitializer = (initializer) => {
43854
43696
  if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
43855
43697
  if (!isNodeOfType(initializer, "Identifier")) return null;
43856
- const resolvedSymbol = resolveConstIdentifierAlias(initializer, scopes);
43857
- if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
43858
- if (!isNodeOfType(resolvedSymbol.initializer, "CallExpression")) return null;
43859
- return resolveTanstackQueryHookName(resolvedSymbol.initializer);
43698
+ const binding = findVariableInitializer(initializer, initializer.name);
43699
+ if (!binding?.initializer || !isConstDeclaredBinding(binding)) return null;
43700
+ if (!isNodeOfType(binding.initializer, "CallExpression")) return null;
43701
+ return resolveTanstackQueryHookName(binding.initializer);
43860
43702
  };
43861
43703
  const queryNoRestDestructuring = defineRule({
43862
43704
  id: "query-no-rest-destructuring",
@@ -43869,7 +43711,7 @@ const queryNoRestDestructuring = defineRule({
43869
43711
  if (!isNodeOfType(node.id, "ObjectPattern")) return;
43870
43712
  if (!node.init) return;
43871
43713
  if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
43872
- const hookName = resolveHookNameFromInitializer(node.init, context.scopes);
43714
+ const hookName = resolveHookNameFromInitializer(node.init);
43873
43715
  if (!hookName) return;
43874
43716
  context.report({
43875
43717
  node: node.id,
@@ -45224,7 +45066,7 @@ const DEFERRABLE_HOOK_NAMES = new Set([
45224
45066
  "useParams",
45225
45067
  "usePathname"
45226
45068
  ]);
45227
- const findHookCallBindings = (componentBody, scopes) => {
45069
+ const findHookCallBindings = (componentBody) => {
45228
45070
  const bindings = [];
45229
45071
  if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
45230
45072
  for (const statement of componentBody.body ?? []) {
@@ -45235,10 +45077,8 @@ const findHookCallBindings = (componentBody, scopes) => {
45235
45077
  const callee = declarator.init.callee;
45236
45078
  if (!isNodeOfType(callee, "Identifier")) continue;
45237
45079
  if (!DEFERRABLE_HOOK_NAMES.has(callee.name)) continue;
45238
- const valueSymbol = scopes.symbolFor(declarator.id);
45239
- if (!valueSymbol) continue;
45240
45080
  bindings.push({
45241
- valueSymbol,
45081
+ valueName: declarator.id.name,
45242
45082
  hookName: callee.name,
45243
45083
  declarator
45244
45084
  });
@@ -45246,49 +45086,6 @@ const findHookCallBindings = (componentBody, scopes) => {
45246
45086
  }
45247
45087
  return bindings;
45248
45088
  };
45249
- const collectExactAliasSymbols = (componentBody, sourceSymbol, scopes) => {
45250
- const symbols = [sourceSymbol];
45251
- const symbolIds = new Set([sourceSymbol.id]);
45252
- const aliasSourceIdentifiers = /* @__PURE__ */ new Set();
45253
- if (!isNodeOfType(componentBody, "BlockStatement")) return {
45254
- symbols,
45255
- aliasSourceIdentifiers
45256
- };
45257
- let didFindAlias = true;
45258
- while (didFindAlias) {
45259
- didFindAlias = false;
45260
- for (const statement of componentBody.body ?? []) {
45261
- if (!isNodeOfType(statement, "VariableDeclaration") || statement.kind !== "const") continue;
45262
- for (const declarator of statement.declarations ?? []) {
45263
- let aliasIdentifier = null;
45264
- let sourceIdentifier = null;
45265
- const initializer = declarator.init ? stripParenExpression(declarator.init) : null;
45266
- const arrayBinding = isNodeOfType(declarator.id, "ArrayPattern") ? declarator.id.elements[0] : null;
45267
- const arrayValueNode = isNodeOfType(initializer, "ArrayExpression") ? initializer.elements[0] : null;
45268
- const arrayValue = arrayValueNode && !isNodeOfType(arrayValueNode, "SpreadElement") ? stripParenExpression(arrayValueNode) : null;
45269
- if (isNodeOfType(declarator.id, "Identifier") && isNodeOfType(initializer, "Identifier")) {
45270
- aliasIdentifier = declarator.id;
45271
- sourceIdentifier = initializer;
45272
- } else if (isNodeOfType(declarator.id, "ArrayPattern") && declarator.id.elements.length === 1 && isNodeOfType(arrayBinding, "Identifier") && isNodeOfType(initializer, "ArrayExpression") && initializer.elements.length === 1 && isNodeOfType(arrayValue, "Identifier")) {
45273
- aliasIdentifier = arrayBinding;
45274
- sourceIdentifier = arrayValue;
45275
- }
45276
- if (!aliasIdentifier || !sourceIdentifier) continue;
45277
- const referencedSymbol = scopes.symbolFor(sourceIdentifier);
45278
- const aliasSymbol = scopes.symbolFor(aliasIdentifier);
45279
- if (!referencedSymbol || !symbolIds.has(referencedSymbol.id) || !aliasSymbol || aliasSymbol.kind !== "const" || symbolIds.has(aliasSymbol.id)) continue;
45280
- symbolIds.add(aliasSymbol.id);
45281
- symbols.push(aliasSymbol);
45282
- aliasSourceIdentifiers.add(sourceIdentifier);
45283
- didFindAlias = true;
45284
- }
45285
- }
45286
- }
45287
- return {
45288
- symbols,
45289
- aliasSourceIdentifiers
45290
- };
45291
- };
45292
45089
  const rerenderDeferReadsHook = defineRule({
45293
45090
  id: "rerender-defer-reads-hook",
45294
45091
  title: "URL hook value only read in handlers",
@@ -45299,13 +45096,15 @@ const rerenderDeferReadsHook = defineRule({
45299
45096
  create: (context) => {
45300
45097
  const checkComponent = (componentBody) => {
45301
45098
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
45302
- const bindings = findHookCallBindings(componentBody, context.scopes);
45099
+ const bindings = findHookCallBindings(componentBody);
45303
45100
  if (bindings.length === 0) return;
45304
45101
  const handlerBindingNames = collectHandlerBindingNames(componentBody);
45305
45102
  for (const binding of bindings) {
45306
- const { symbols, aliasSourceIdentifiers } = collectExactAliasSymbols(componentBody, binding.valueSymbol, context.scopes);
45307
45103
  const referenceLocations = [];
45308
- for (const symbol of symbols) for (const reference of symbol.references) if (!aliasSourceIdentifiers.has(reference.identifier)) referenceLocations.push(reference.identifier);
45104
+ walkAst(componentBody, (child) => {
45105
+ if (isNodeOfType(binding.declarator, "VariableDeclarator") && child === binding.declarator.id) return;
45106
+ if (isNodeOfType(child, "Identifier") && child.name === binding.valueName) referenceLocations.push(child);
45107
+ });
45309
45108
  if (referenceLocations.length === 0) continue;
45310
45109
  if (!referenceLocations.every((ref) => isInsideEventHandler(ref, handlerBindingNames))) continue;
45311
45110
  context.report({
@@ -53254,8 +53053,7 @@ const serverCacheWithObjectLiteral = defineRule({
53254
53053
  if (!isNodeOfType(node.callee, "Identifier")) return;
53255
53054
  if (!cachedFunctionNames.has(node.callee.name)) return;
53256
53055
  const firstArg = node.arguments?.[0];
53257
- if (!firstArg || isNodeOfType(firstArg, "SpreadElement")) return;
53258
- if (!isNodeOfType(unwrapObjectIntegrityExpression(firstArg, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES), "ObjectExpression")) return;
53056
+ if (!isNodeOfType(firstArg, "ObjectExpression")) return;
53259
53057
  context.report({
53260
53058
  node,
53261
53059
  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.e141d90",
3
+ "version": "0.7.6-dev.eb09902",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",