oxlint-plugin-react-doctor 0.7.4-dev.63fc41f → 0.7.4-dev.6821fe0

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 +159 -19
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -553,7 +553,7 @@ const EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS = new Set([
553
553
  "ResizeObserver",
554
554
  "PerformanceObserver"
555
555
  ]);
556
- const STORAGE_OBJECTS$1 = new Set(["localStorage", "sessionStorage"]);
556
+ const STORAGE_OBJECTS = new Set(["localStorage", "sessionStorage"]);
557
557
  //#endregion
558
558
  //#region src/plugin/constants/react.ts
559
559
  const INDEX_PARAMETER_NAMES = new Set([
@@ -5139,8 +5139,10 @@ const STORAGE_GLOBALS = new Set([
5139
5139
  const SENSITIVE_KEY_PATTERN = /token|jwt|secret|password|passwd|credential|api[-_]?key|bearer|private[-_]?key/i;
5140
5140
  const NON_AUTH_TOKEN_PATTERN = /csrf|xsrf|device|fcm|apns|push|design|tokeniz|syntax|css|theme|color/i;
5141
5141
  const STRONG_AUTH_KEY_PATTERN = /jwt|secret|password|passwd|credential|private[-_]?key|api[-_]?key|bearer|access[-_]?token|refresh[-_]?token|auth[-_]?token|id[-_]?token|session/i;
5142
+ const PRODUCT_API_KEY_COLLECTION_PATTERN = /[._:-](?:created|generated|saved)[-_]?api[-_]?keys$/i;
5142
5143
  const isAuthCredentialKey = (key) => {
5143
5144
  if (!SENSITIVE_KEY_PATTERN.test(key)) return false;
5145
+ if (PRODUCT_API_KEY_COLLECTION_PATTERN.test(key)) return false;
5144
5146
  if (NON_AUTH_TOKEN_PATTERN.test(key) && !STRONG_AUTH_KEY_PATTERN.test(key)) return false;
5145
5147
  return true;
5146
5148
  };
@@ -6135,7 +6137,6 @@ const isGlobalMethodCall = (node, objectName, methodName) => {
6135
6137
  //#region src/plugin/rules/client/client-localstorage-no-version.ts
6136
6138
  const VERSIONED_KEY_PATTERN = /(?:[._:-]v\d+|@\d+|\bv\d+\b)/i;
6137
6139
  const CAMEL_CASE_VERSIONED_KEY_PATTERN = /[a-z]V\d+/;
6138
- const STORAGE_OBJECTS = new Set(["localStorage", "sessionStorage"]);
6139
6140
  const isVersionedKey = (key) => VERSIONED_KEY_PATTERN.test(key) || CAMEL_CASE_VERSIONED_KEY_PATTERN.test(key);
6140
6141
  const isJsonStringifyCall = (node) => isGlobalMethodCall(node, "JSON", "stringify");
6141
6142
  const resolveStringKey = (keyArg, context) => {
@@ -6158,7 +6159,7 @@ const clientLocalstorageNoVersion = defineRule({
6158
6159
  if (!isNodeOfType(node.callee, "MemberExpression")) return;
6159
6160
  const receiver = stripParenExpression(node.callee.object);
6160
6161
  if (!isNodeOfType(receiver, "Identifier")) return;
6161
- if (!STORAGE_OBJECTS.has(receiver.name)) return;
6162
+ if (receiver.name !== "localStorage") return;
6162
6163
  if (!isNodeOfType(node.callee.property, "Identifier")) return;
6163
6164
  if (node.callee.property.name !== "setItem") return;
6164
6165
  const keyArg = node.arguments?.[0];
@@ -8938,7 +8939,7 @@ const getListenerAbortControllerKey = (usage, context) => {
8938
8939
  }
8939
8940
  return null;
8940
8941
  };
8941
- const SYNCHRONOUS_ITERATOR_METHOD_NAMES$1 = new Set([
8942
+ const SYNCHRONOUS_ITERATOR_METHOD_NAMES$2 = new Set([
8942
8943
  "every",
8943
8944
  "filter",
8944
8945
  "flatMap",
@@ -8954,7 +8955,7 @@ const isSynchronousIteratorCallback = (functionNode) => {
8954
8955
  const callee = stripParenExpression(callNode.callee);
8955
8956
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
8956
8957
  if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments?.[1] === functionNode;
8957
- return SYNCHRONOUS_ITERATOR_METHOD_NAMES$1.has(callee.property.name) && callNode.arguments?.[0] === functionNode;
8958
+ return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments?.[0] === functionNode;
8958
8959
  };
8959
8960
  const findDirectCallForReference = (identifier) => {
8960
8961
  const expressionRoot = findTransparentExpressionRoot(identifier);
@@ -10589,6 +10590,19 @@ const isUnstableInitializer = (node) => {
10589
10590
  if (isNodeOfType(stripped, "LogicalExpression")) return isUnstableInitializer(stripped.left) || isUnstableInitializer(stripped.right);
10590
10591
  return isNodeOfType(stripped, "ObjectExpression") || isNodeOfType(stripped, "ArrayExpression") || isNodeOfType(stripped, "ClassExpression") || isNodeOfType(stripped, "ClassDeclaration") || isNodeOfType(stripped, "JSXElement") || isNodeOfType(stripped, "JSXFragment") || isNodeOfType(stripped, "AssignmentExpression") || isNodeOfType(stripped, "NewExpression");
10591
10592
  };
10593
+ const isPotentiallyFreshComparedValue = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
10594
+ const candidate = unwrapExpression$3(node);
10595
+ if (isUnstableInitializer(candidate)) return true;
10596
+ if (isNodeOfType(candidate, "ConditionalExpression")) return isPotentiallyFreshComparedValue(candidate.consequent, scopes, visitedSymbolIds) || isPotentiallyFreshComparedValue(candidate.alternate, scopes, visitedSymbolIds);
10597
+ if (isNodeOfType(candidate, "LogicalExpression")) return isPotentiallyFreshComparedValue(candidate.left, scopes, visitedSymbolIds) || isPotentiallyFreshComparedValue(candidate.right, scopes, visitedSymbolIds);
10598
+ if (!isNodeOfType(candidate, "Identifier")) return false;
10599
+ const symbol = scopes.symbolFor(candidate);
10600
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
10601
+ if (symbol.kind === "let" || symbol.kind === "var") return true;
10602
+ if (symbol.kind !== "const" || !symbol.initializer) return false;
10603
+ visitedSymbolIds.add(symbol.id);
10604
+ return isPotentiallyFreshComparedValue(symbol.initializer, scopes, visitedSymbolIds);
10605
+ };
10592
10606
  const isExtraDepAllowedForHook = (hookName, node, scopes) => {
10593
10607
  if (!isExtraReactiveDepAllowed(node, scopes)) return false;
10594
10608
  if (EFFECT_HOOKS_ALLOWING_EXTRA_REACTIVE_DEPS.has(hookName)) return true;
@@ -10602,6 +10616,52 @@ const isStableSetterLikeSymbol = (symbol, scopes) => {
10602
10616
  if (!symbolHasStableHookOrigin(symbol, scopes)) return false;
10603
10617
  return symbol.name.startsWith("set") || symbol.name.startsWith("dispatch") || symbol.name.startsWith("startTransition");
10604
10618
  };
10619
+ const isConvergingFunctionalUpdater = (node, scopes) => {
10620
+ const updater = unwrapExpression$3(node);
10621
+ if (!isNodeOfType(updater, "ArrowFunctionExpression") && !isNodeOfType(updater, "FunctionExpression")) return false;
10622
+ const previousValueParameter = updater.params?.[0];
10623
+ if (!previousValueParameter || !isNodeOfType(previousValueParameter, "Identifier")) return false;
10624
+ let returnedExpression = updater.body;
10625
+ if (isNodeOfType(updater.body, "BlockStatement")) {
10626
+ returnedExpression = null;
10627
+ for (const statement of updater.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument) {
10628
+ returnedExpression = statement.argument;
10629
+ break;
10630
+ }
10631
+ }
10632
+ const conditional = returnedExpression ? unwrapExpression$3(returnedExpression) : null;
10633
+ if (!conditional || !isNodeOfType(conditional, "ConditionalExpression")) return false;
10634
+ const test = unwrapExpression$3(conditional.test);
10635
+ if (!isNodeOfType(test, "BinaryExpression") || ![
10636
+ "===",
10637
+ "!==",
10638
+ "==",
10639
+ "!="
10640
+ ].includes(test.operator)) return false;
10641
+ const isPreviousValue = (expression) => {
10642
+ const candidate = unwrapExpression$3(expression);
10643
+ return isNodeOfType(candidate, "Identifier") && candidate.name === previousValueParameter.name;
10644
+ };
10645
+ let comparedValue = null;
10646
+ if (isPreviousValue(test.left)) comparedValue = test.right;
10647
+ else if (isPreviousValue(test.right)) comparedValue = test.left;
10648
+ if (!comparedValue) return false;
10649
+ if (isPotentiallyFreshComparedValue(comparedValue, scopes)) return false;
10650
+ const isSameComparedValue = (expression) => {
10651
+ const candidate = unwrapExpression$3(expression);
10652
+ const compared = unwrapExpression$3(comparedValue);
10653
+ if (isNodeOfType(candidate, "Identifier") && isNodeOfType(compared, "Identifier")) return candidate.name === compared.name;
10654
+ if (isNodeOfType(candidate, "Literal") && isNodeOfType(compared, "Literal")) return candidate.value === compared.value;
10655
+ return false;
10656
+ };
10657
+ return test.operator === "===" || test.operator === "==" ? isPreviousValue(conditional.consequent) && isSameComparedValue(conditional.alternate) : isSameComparedValue(conditional.consequent) && isPreviousValue(conditional.alternate);
10658
+ };
10659
+ const isGuardedStableSetterCall = (identifier, symbol, scopes) => {
10660
+ const parent = identifier.parent;
10661
+ if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier || !isStableSetterLikeSymbol(symbol, scopes)) return false;
10662
+ const writtenValue = parent.arguments?.[0];
10663
+ return Boolean(writtenValue && isConvergingFunctionalUpdater(writtenValue, scopes));
10664
+ };
10605
10665
  const findStableSetterReference = (node, scopes) => {
10606
10666
  let setterName = null;
10607
10667
  const visit = (current) => {
@@ -10609,7 +10669,7 @@ const findStableSetterReference = (node, scopes) => {
10609
10669
  if (current !== node && (isNodeOfType(current, "FunctionDeclaration") || isNodeOfType(current, "FunctionExpression") || isNodeOfType(current, "ArrowFunctionExpression"))) return;
10610
10670
  if (isNodeOfType(current, "Identifier")) {
10611
10671
  const symbol = scopes.referenceFor(current)?.resolvedSymbol;
10612
- if (symbol && isStableSetterLikeSymbol(symbol, scopes)) {
10672
+ if (symbol && isStableSetterLikeSymbol(symbol, scopes) && !isGuardedStableSetterCall(current, symbol, scopes)) {
10613
10673
  setterName = symbol.name;
10614
10674
  return;
10615
10675
  }
@@ -13502,7 +13562,7 @@ const jsCacheStorage = defineRule({
13502
13562
  CallExpression(node) {
13503
13563
  if (!isMemberProperty(node.callee, "getItem")) return;
13504
13564
  const receiver = stripParenExpression(node.callee.object);
13505
- if (!isNodeOfType(receiver, "Identifier") || !STORAGE_OBJECTS$1.has(receiver.name)) return;
13565
+ if (!isNodeOfType(receiver, "Identifier") || !STORAGE_OBJECTS.has(receiver.name)) return;
13506
13566
  if (!isNodeOfType(node.arguments?.[0], "Literal")) return;
13507
13567
  const storageReadCounts = storageReadCountStack[storageReadCountStack.length - 1];
13508
13568
  const storageKey = String(node.arguments[0].value);
@@ -13769,6 +13829,11 @@ const jsEarlyExit = defineRule({
13769
13829
  });
13770
13830
  //#endregion
13771
13831
  //#region src/plugin/rules/js-performance/js-flatmap-filter.ts
13832
+ const BOUNDED_PIPELINE_SOURCE_METHOD_NAMES = new Set(["slice", "split"]);
13833
+ const isBoundedPipelineSource = (node) => {
13834
+ const receiver = stripParenExpression(node);
13835
+ return isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "MemberExpression") && isNodeOfType(receiver.callee.property, "Identifier") && BOUNDED_PIPELINE_SOURCE_METHOD_NAMES.has(receiver.callee.property.name);
13836
+ };
13772
13837
  const jsFlatmapFilter = defineRule({
13773
13838
  id: "js-flatmap-filter",
13774
13839
  title: ".map().filter(Boolean) loops twice",
@@ -13786,6 +13851,7 @@ const jsFlatmapFilter = defineRule({
13786
13851
  if (!isNodeOfType(innerCall, "CallExpression") || !isNodeOfType(innerCall.callee, "MemberExpression") || !isNodeOfType(innerCall.callee.property, "Identifier")) return;
13787
13852
  if (innerCall.callee.property.name !== "map") return;
13788
13853
  const receiver = stripParenExpression(innerCall.callee.object);
13854
+ if (receiver && isBoundedPipelineSource(receiver)) return;
13789
13855
  if (receiver && isNodeOfType(receiver, "ArrayExpression")) {
13790
13856
  const elements = receiver.elements ?? [];
13791
13857
  if (elements.length > 0 && elements.length <= 8 && elements.every((element) => element == null || !isNodeOfType(element, "SpreadElement"))) return;
@@ -22752,7 +22818,7 @@ const readsPostMountValueThroughLocals = (root, effectFn, options = {}, visitedL
22752
22818
  };
22753
22819
  //#endregion
22754
22820
  //#region src/plugin/rules/state-and-effects/utils/collect-effect-state-write-facts.ts
22755
- const SYNCHRONOUS_ITERATOR_METHOD_NAMES = new Set([
22821
+ const SYNCHRONOUS_ITERATOR_METHOD_NAMES$1 = new Set([
22756
22822
  "every",
22757
22823
  "filter",
22758
22824
  "find",
@@ -23060,7 +23126,7 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilena
23060
23126
  const calleeName = getCallCalleeName$1(child);
23061
23127
  const memberName = getStaticMemberName(callee);
23062
23128
  const isDeferringCall = calleeName !== null && DEFERRING_CALLEE_NAMES.has(calleeName) || memberName !== null && DEFERRING_MEMBER_NAMES.has(memberName);
23063
- const isIteratorCall = memberName !== null && SYNCHRONOUS_ITERATOR_METHOD_NAMES.has(memberName) && isNodeOfType(callee, "MemberExpression");
23129
+ const isIteratorCall = memberName !== null && SYNCHRONOUS_ITERATOR_METHOD_NAMES$1.has(memberName) && isNodeOfType(callee, "MemberExpression");
23064
23130
  const enqueueFrame = (callableNode, argumentsForCallable, isDeferred, introducedBindings, allowWithoutInvocationEvidence = false) => {
23065
23131
  const callable = resolveWrappedCallable(analysis, callableNode);
23066
23132
  if (!callable || callable.async === true || callable === effectFunction) return;
@@ -23297,10 +23363,15 @@ const isLocallyConstructedObjectMember = (reference, memberExpression) => isNode
23297
23363
  const definitionNode = definition.node;
23298
23364
  return isNodeOfType(definitionNode, "VariableDeclarator") && Boolean(definitionNode.init) && (isNodeOfType(definitionNode.init, "ObjectExpression") || isNodeOfType(definitionNode.init, "ArrayExpression"));
23299
23365
  }) === true;
23366
+ const getUseRefDeclarator = (analysis, memberExpression) => {
23367
+ if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !isNodeOfType(memberExpression.object, "Identifier")) return null;
23368
+ return getRef(analysis, memberExpression.object)?.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator") && isNodeOfType(definitionNode.init, "CallExpression") && getCallCalleeName$1(definitionNode.init) === "useRef") ?? null;
23369
+ };
23300
23370
  const collectValueEvidence = (analysis, expression, frame, remainingCallFrames, visitedBindings = /* @__PURE__ */ new Set()) => {
23301
23371
  const node = stripParenExpression(expression);
23302
23372
  const evidence = emptyEvidence();
23303
- if (readsPostMountValue(node) || readsPostMountValueThroughLocals(node, frame.functionNode)) {
23373
+ const useRefDeclarator = getUseRefDeclarator(analysis, node);
23374
+ if (!useRefDeclarator && (readsPostMountValue(node) || readsPostMountValueThroughLocals(node, frame.functionNode))) {
23304
23375
  evidence.readsExternalValue = true;
23305
23376
  return evidence;
23306
23377
  }
@@ -23342,7 +23413,11 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23342
23413
  evidence.hasUnknownSource = true;
23343
23414
  return evidence;
23344
23415
  }
23345
- if (reference.resolved.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init)) {
23416
+ const nonInitializerWrites = reference.resolved.references.filter((candidateReference) => candidateReference.isWrite() && !candidateReference.init);
23417
+ if (nonInitializerWrites.length > 0) {
23418
+ const writtenIdentifier = nonInitializerWrites[0]?.identifier;
23419
+ const assignment = writtenIdentifier.parent;
23420
+ if (nonInitializerWrites.length === 1 && assignment && isNodeOfType(assignment, "AssignmentExpression") && assignment.operator === "=" && assignment.left === writtenIdentifier) return collectValueEvidence(analysis, assignment.right, frame, remainingCallFrames, visitedBindings);
23346
23421
  evidence.hasUnknownSource = true;
23347
23422
  return evidence;
23348
23423
  }
@@ -23358,6 +23433,40 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23358
23433
  return collectValueEvidence(analysis, initializer.init, frame, remainingCallFrames, visitedBindings);
23359
23434
  }
23360
23435
  if (isNodeOfType(node, "MemberExpression")) {
23436
+ if (getStaticMemberName(node) === "current" && isNodeOfType(node.object, "Identifier")) {
23437
+ const refBinding = getRef(analysis, node.object)?.resolved;
23438
+ const refDeclarator = useRefDeclarator;
23439
+ if (refBinding && refDeclarator && isNodeOfType(refDeclarator, "VariableDeclarator") && isNodeOfType(refDeclarator.init, "CallExpression")) {
23440
+ if (visitedBindings.has(refBinding)) {
23441
+ evidence.hasUnknownSource = true;
23442
+ return evidence;
23443
+ }
23444
+ const refVisitedBindings = new Set(visitedBindings);
23445
+ refVisitedBindings.add(refBinding);
23446
+ const initialValue = refDeclarator.init.arguments?.[0];
23447
+ if (initialValue) mergeEvidence(evidence, collectValueEvidence(analysis, initialValue, frame, remainingCallFrames, new Set(refVisitedBindings)));
23448
+ for (const candidateReference of refBinding.references) {
23449
+ if (candidateReference.init) continue;
23450
+ const identifier = candidateReference.identifier;
23451
+ const member = identifier.parent;
23452
+ if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName(member) !== "current") {
23453
+ evidence.hasUnknownSource = true;
23454
+ continue;
23455
+ }
23456
+ const memberParent = member.parent;
23457
+ if (memberParent && isNodeOfType(memberParent, "AssignmentExpression") && memberParent.left === member) {
23458
+ if (memberParent.operator !== "=") {
23459
+ evidence.hasUnknownSource = true;
23460
+ continue;
23461
+ }
23462
+ mergeEvidence(evidence, collectValueEvidence(analysis, memberParent.right, frame, remainingCallFrames, new Set(refVisitedBindings)));
23463
+ continue;
23464
+ }
23465
+ if (memberParent && isNodeOfType(memberParent, "UpdateExpression")) evidence.hasUnknownSource = true;
23466
+ }
23467
+ return evidence;
23468
+ }
23469
+ }
23361
23470
  if (isNodeOfType(node.object, "Identifier")) {
23362
23471
  const objectReference = getRef(analysis, node.object);
23363
23472
  if (objectReference && isLocallyConstructedObjectMember(objectReference, node)) {
@@ -29136,6 +29245,18 @@ const MUTATING_COLLECTION_METHOD_NAMES = new Set([
29136
29245
  "set",
29137
29246
  "add"
29138
29247
  ]);
29248
+ const SYNCHRONOUS_ITERATOR_METHOD_NAMES = new Set([
29249
+ "every",
29250
+ "filter",
29251
+ "find",
29252
+ "findIndex",
29253
+ "flatMap",
29254
+ "forEach",
29255
+ "map",
29256
+ "reduce",
29257
+ "reduceRight",
29258
+ "some"
29259
+ ]);
29139
29260
  const getMemberRootBindingName = (node, scope) => {
29140
29261
  let currentNode = node;
29141
29262
  while (isNodeOfType(currentNode, "MemberExpression")) currentNode = currentNode.object;
@@ -29189,6 +29310,26 @@ const addMutatingCollectionCallDependencies = (graph, expression, scope, eventHa
29189
29310
  addDependencyNames(dependencyNames, controlDependencyNames);
29190
29311
  addDependencies(graph, receiverRootName, dependencyNames);
29191
29312
  };
29313
+ const addIteratorMutationDependencies = (graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames) => {
29314
+ if (!isNodeOfType(expression, "CallExpression")) return;
29315
+ if (!isNodeOfType(expression.callee, "MemberExpression")) return;
29316
+ const methodName = getStaticMemberPropertyName(expression.callee);
29317
+ if (!methodName || !SYNCHRONOUS_ITERATOR_METHOD_NAMES.has(methodName)) return;
29318
+ const iteratorDependencyNames = collectScopedReferenceNames(expression.callee.object, scope, eventHandlerReferenceNames);
29319
+ addDependencyNames(iteratorDependencyNames, controlDependencyNames);
29320
+ for (const argument of expression.arguments ?? []) {
29321
+ if (!isNodeOfType(argument, "ArrowFunctionExpression") && !isNodeOfType(argument, "FunctionExpression")) continue;
29322
+ walkAst(argument.body, (node) => {
29323
+ if (node !== argument.body && isFunctionLike$1(node)) return false;
29324
+ if (!isNodeOfType(node, "CallExpression")) return;
29325
+ if (!isNodeOfType(node.callee, "MemberExpression")) return;
29326
+ const nestedMethodName = getStaticMemberPropertyName(node.callee);
29327
+ if (!nestedMethodName || !MUTATING_COLLECTION_METHOD_NAMES.has(nestedMethodName)) return;
29328
+ const receiverRootName = getMemberRootBindingName(node.callee.object, scope);
29329
+ if (receiverRootName) addDependencies(graph, receiverRootName, iteratorDependencyNames);
29330
+ });
29331
+ }
29332
+ };
29192
29333
  const collectExpressionDependencies = (graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames) => {
29193
29334
  if (isNodeOfType(expression, "AssignmentExpression")) {
29194
29335
  addAssignmentExpressionDependencies(graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames);
@@ -29196,6 +29337,7 @@ const collectExpressionDependencies = (graph, expression, scope, eventHandlerRef
29196
29337
  }
29197
29338
  if (isNodeOfType(expression, "CallExpression")) {
29198
29339
  addMutatingCollectionCallDependencies(graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames);
29340
+ addIteratorMutationDependencies(graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames);
29199
29341
  return;
29200
29342
  }
29201
29343
  if (isNodeOfType(expression, "SequenceExpression")) {
@@ -30236,28 +30378,26 @@ const noGiantComponent = defineRule({
30236
30378
  const lineCount = bodyNode.loc.end.line - bodyNode.loc.start.line + 1;
30237
30379
  return lineCount > 300 ? lineCount : null;
30238
30380
  };
30239
- const reportOversizedComponent = (nameNode, componentName, lineCount) => {
30381
+ const reportOversizedComponent = (nameNode, componentName) => {
30240
30382
  context.report({
30241
30383
  node: nameNode,
30242
- message: `Component "${componentName}" is ${lineCount} lines long, which is hard to read & change. Split it into a few smaller components.`
30384
+ message: `Component "${componentName}" is over 300 lines long, which is hard to read & change. Split it into a few smaller components.`
30243
30385
  });
30244
30386
  };
30245
30387
  return {
30246
30388
  FunctionDeclaration(node) {
30247
30389
  if (!node.id?.name || !isUppercaseName(node.id.name)) return;
30248
- const lineCount = getOversizedComponentLineCount(node);
30249
- if (lineCount === null) return;
30390
+ if (getOversizedComponentLineCount(node) === null) return;
30250
30391
  if (!functionContainsReactRenderOutput(node, context.scopes)) return;
30251
- reportOversizedComponent(node.id, node.id.name, lineCount);
30392
+ reportOversizedComponent(node.id, node.id.name);
30252
30393
  },
30253
30394
  VariableDeclarator(node) {
30254
30395
  if (!isNodeOfType(node.id, "Identifier") || !isUppercaseName(node.id.name)) return;
30255
30396
  const functionNode = unwrapReactHocFunction(node.init);
30256
30397
  if (!functionNode) return;
30257
- const lineCount = getOversizedComponentLineCount(functionNode);
30258
- if (lineCount === null) return;
30398
+ if (getOversizedComponentLineCount(functionNode) === null) return;
30259
30399
  if (!functionContainsReactRenderOutput(functionNode, context.scopes)) return;
30260
- reportOversizedComponent(node.id, node.id.name, lineCount);
30400
+ reportOversizedComponent(node.id, node.id.name);
30261
30401
  }
30262
30402
  };
30263
30403
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.4-dev.63fc41f",
3
+ "version": "0.7.4-dev.6821fe0",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",