oxlint-plugin-react-doctor 0.7.4-dev.6821fe0 → 0.7.4-dev.7bbb792

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.
package/dist/index.d.ts CHANGED
@@ -5296,29 +5296,6 @@ declare const REACT_DOCTOR_RULES: readonly [{
5296
5296
  readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
5297
5297
  readonly create: (context: RuleContext) => RuleVisitors;
5298
5298
  };
5299
- }, {
5300
- readonly key: "react-doctor/no-prop-callback-in-render";
5301
- readonly id: "no-prop-callback-in-render";
5302
- readonly source: "react-doctor";
5303
- readonly originallyExternal: false;
5304
- readonly rule: {
5305
- readonly framework: "global";
5306
- readonly category: "Bugs";
5307
- readonly requires: readonly Capability[];
5308
- readonly id: string;
5309
- readonly title?: string;
5310
- readonly severity: RuleSeverity;
5311
- readonly disabledWhen?: ReadonlyArray<Capability>;
5312
- readonly tags?: ReadonlyArray<string>;
5313
- readonly matchByOccurrence?: boolean;
5314
- readonly defaultEnabled?: boolean;
5315
- readonly lifecycle?: "retired";
5316
- readonly scan?: FileScan;
5317
- readonly committedFilesOnly?: boolean;
5318
- readonly recommendation?: string;
5319
- readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
5320
- readonly create: (context: RuleContext) => RuleVisitors;
5321
- };
5322
5299
  }, {
5323
5300
  readonly key: "react-doctor/no-prop-types";
5324
5301
  readonly id: "no-prop-types";
@@ -14562,29 +14539,6 @@ declare const RULES: readonly [{
14562
14539
  readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
14563
14540
  readonly create: (context: RuleContext) => RuleVisitors;
14564
14541
  };
14565
- }, {
14566
- readonly key: "react-doctor/no-prop-callback-in-render";
14567
- readonly id: "no-prop-callback-in-render";
14568
- readonly source: "react-doctor";
14569
- readonly originallyExternal: false;
14570
- readonly rule: {
14571
- readonly framework: "global";
14572
- readonly category: "Bugs";
14573
- readonly requires: readonly Capability[];
14574
- readonly id: string;
14575
- readonly title?: string;
14576
- readonly severity: RuleSeverity;
14577
- readonly disabledWhen?: ReadonlyArray<Capability>;
14578
- readonly tags?: ReadonlyArray<string>;
14579
- readonly matchByOccurrence?: boolean;
14580
- readonly defaultEnabled?: boolean;
14581
- readonly lifecycle?: "retired";
14582
- readonly scan?: FileScan;
14583
- readonly committedFilesOnly?: boolean;
14584
- readonly recommendation?: string;
14585
- readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
14586
- readonly create: (context: RuleContext) => RuleVisitors;
14587
- };
14588
14542
  }, {
14589
14543
  readonly key: "react-doctor/no-prop-types";
14590
14544
  readonly id: "no-prop-types";
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 = new Set(["localStorage", "sessionStorage"]);
556
+ const STORAGE_OBJECTS$1 = new Set(["localStorage", "sessionStorage"]);
557
557
  //#endregion
558
558
  //#region src/plugin/constants/react.ts
559
559
  const INDEX_PARAMETER_NAMES = new Set([
@@ -5139,10 +5139,8 @@ 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;
5143
5142
  const isAuthCredentialKey = (key) => {
5144
5143
  if (!SENSITIVE_KEY_PATTERN.test(key)) return false;
5145
- if (PRODUCT_API_KEY_COLLECTION_PATTERN.test(key)) return false;
5146
5144
  if (NON_AUTH_TOKEN_PATTERN.test(key) && !STRONG_AUTH_KEY_PATTERN.test(key)) return false;
5147
5145
  return true;
5148
5146
  };
@@ -6137,6 +6135,7 @@ const isGlobalMethodCall = (node, objectName, methodName) => {
6137
6135
  //#region src/plugin/rules/client/client-localstorage-no-version.ts
6138
6136
  const VERSIONED_KEY_PATTERN = /(?:[._:-]v\d+|@\d+|\bv\d+\b)/i;
6139
6137
  const CAMEL_CASE_VERSIONED_KEY_PATTERN = /[a-z]V\d+/;
6138
+ const STORAGE_OBJECTS = new Set(["localStorage", "sessionStorage"]);
6140
6139
  const isVersionedKey = (key) => VERSIONED_KEY_PATTERN.test(key) || CAMEL_CASE_VERSIONED_KEY_PATTERN.test(key);
6141
6140
  const isJsonStringifyCall = (node) => isGlobalMethodCall(node, "JSON", "stringify");
6142
6141
  const resolveStringKey = (keyArg, context) => {
@@ -6159,7 +6158,7 @@ const clientLocalstorageNoVersion = defineRule({
6159
6158
  if (!isNodeOfType(node.callee, "MemberExpression")) return;
6160
6159
  const receiver = stripParenExpression(node.callee.object);
6161
6160
  if (!isNodeOfType(receiver, "Identifier")) return;
6162
- if (receiver.name !== "localStorage") return;
6161
+ if (!STORAGE_OBJECTS.has(receiver.name)) return;
6163
6162
  if (!isNodeOfType(node.callee.property, "Identifier")) return;
6164
6163
  if (node.callee.property.name !== "setItem") return;
6165
6164
  const keyArg = node.arguments?.[0];
@@ -8939,7 +8938,7 @@ const getListenerAbortControllerKey = (usage, context) => {
8939
8938
  }
8940
8939
  return null;
8941
8940
  };
8942
- const SYNCHRONOUS_ITERATOR_METHOD_NAMES$2 = new Set([
8941
+ const SYNCHRONOUS_ITERATOR_METHOD_NAMES$1 = new Set([
8943
8942
  "every",
8944
8943
  "filter",
8945
8944
  "flatMap",
@@ -8955,7 +8954,7 @@ const isSynchronousIteratorCallback = (functionNode) => {
8955
8954
  const callee = stripParenExpression(callNode.callee);
8956
8955
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
8957
8956
  if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments?.[1] === functionNode;
8958
- return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments?.[0] === functionNode;
8957
+ return SYNCHRONOUS_ITERATOR_METHOD_NAMES$1.has(callee.property.name) && callNode.arguments?.[0] === functionNode;
8959
8958
  };
8960
8959
  const findDirectCallForReference = (identifier) => {
8961
8960
  const expressionRoot = findTransparentExpressionRoot(identifier);
@@ -10590,19 +10589,6 @@ const isUnstableInitializer = (node) => {
10590
10589
  if (isNodeOfType(stripped, "LogicalExpression")) return isUnstableInitializer(stripped.left) || isUnstableInitializer(stripped.right);
10591
10590
  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");
10592
10591
  };
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
- };
10606
10592
  const isExtraDepAllowedForHook = (hookName, node, scopes) => {
10607
10593
  if (!isExtraReactiveDepAllowed(node, scopes)) return false;
10608
10594
  if (EFFECT_HOOKS_ALLOWING_EXTRA_REACTIVE_DEPS.has(hookName)) return true;
@@ -10616,52 +10602,6 @@ const isStableSetterLikeSymbol = (symbol, scopes) => {
10616
10602
  if (!symbolHasStableHookOrigin(symbol, scopes)) return false;
10617
10603
  return symbol.name.startsWith("set") || symbol.name.startsWith("dispatch") || symbol.name.startsWith("startTransition");
10618
10604
  };
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
- };
10665
10605
  const findStableSetterReference = (node, scopes) => {
10666
10606
  let setterName = null;
10667
10607
  const visit = (current) => {
@@ -10669,7 +10609,7 @@ const findStableSetterReference = (node, scopes) => {
10669
10609
  if (current !== node && (isNodeOfType(current, "FunctionDeclaration") || isNodeOfType(current, "FunctionExpression") || isNodeOfType(current, "ArrowFunctionExpression"))) return;
10670
10610
  if (isNodeOfType(current, "Identifier")) {
10671
10611
  const symbol = scopes.referenceFor(current)?.resolvedSymbol;
10672
- if (symbol && isStableSetterLikeSymbol(symbol, scopes) && !isGuardedStableSetterCall(current, symbol, scopes)) {
10612
+ if (symbol && isStableSetterLikeSymbol(symbol, scopes)) {
10673
10613
  setterName = symbol.name;
10674
10614
  return;
10675
10615
  }
@@ -13562,7 +13502,7 @@ const jsCacheStorage = defineRule({
13562
13502
  CallExpression(node) {
13563
13503
  if (!isMemberProperty(node.callee, "getItem")) return;
13564
13504
  const receiver = stripParenExpression(node.callee.object);
13565
- if (!isNodeOfType(receiver, "Identifier") || !STORAGE_OBJECTS.has(receiver.name)) return;
13505
+ if (!isNodeOfType(receiver, "Identifier") || !STORAGE_OBJECTS$1.has(receiver.name)) return;
13566
13506
  if (!isNodeOfType(node.arguments?.[0], "Literal")) return;
13567
13507
  const storageReadCounts = storageReadCountStack[storageReadCountStack.length - 1];
13568
13508
  const storageKey = String(node.arguments[0].value);
@@ -13829,11 +13769,6 @@ const jsEarlyExit = defineRule({
13829
13769
  });
13830
13770
  //#endregion
13831
13771
  //#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
- };
13837
13772
  const jsFlatmapFilter = defineRule({
13838
13773
  id: "js-flatmap-filter",
13839
13774
  title: ".map().filter(Boolean) loops twice",
@@ -13851,7 +13786,6 @@ const jsFlatmapFilter = defineRule({
13851
13786
  if (!isNodeOfType(innerCall, "CallExpression") || !isNodeOfType(innerCall.callee, "MemberExpression") || !isNodeOfType(innerCall.callee.property, "Identifier")) return;
13852
13787
  if (innerCall.callee.property.name !== "map") return;
13853
13788
  const receiver = stripParenExpression(innerCall.callee.object);
13854
- if (receiver && isBoundedPipelineSource(receiver)) return;
13855
13789
  if (receiver && isNodeOfType(receiver, "ArrayExpression")) {
13856
13790
  const elements = receiver.elements ?? [];
13857
13791
  if (elements.length > 0 && elements.length <= 8 && elements.every((element) => element == null || !isNodeOfType(element, "SpreadElement"))) return;
@@ -22818,7 +22752,7 @@ const readsPostMountValueThroughLocals = (root, effectFn, options = {}, visitedL
22818
22752
  };
22819
22753
  //#endregion
22820
22754
  //#region src/plugin/rules/state-and-effects/utils/collect-effect-state-write-facts.ts
22821
- const SYNCHRONOUS_ITERATOR_METHOD_NAMES$1 = new Set([
22755
+ const SYNCHRONOUS_ITERATOR_METHOD_NAMES = new Set([
22822
22756
  "every",
22823
22757
  "filter",
22824
22758
  "find",
@@ -23126,7 +23060,7 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilena
23126
23060
  const calleeName = getCallCalleeName$1(child);
23127
23061
  const memberName = getStaticMemberName(callee);
23128
23062
  const isDeferringCall = calleeName !== null && DEFERRING_CALLEE_NAMES.has(calleeName) || memberName !== null && DEFERRING_MEMBER_NAMES.has(memberName);
23129
- const isIteratorCall = memberName !== null && SYNCHRONOUS_ITERATOR_METHOD_NAMES$1.has(memberName) && isNodeOfType(callee, "MemberExpression");
23063
+ const isIteratorCall = memberName !== null && SYNCHRONOUS_ITERATOR_METHOD_NAMES.has(memberName) && isNodeOfType(callee, "MemberExpression");
23130
23064
  const enqueueFrame = (callableNode, argumentsForCallable, isDeferred, introducedBindings, allowWithoutInvocationEvidence = false) => {
23131
23065
  const callable = resolveWrappedCallable(analysis, callableNode);
23132
23066
  if (!callable || callable.async === true || callable === effectFunction) return;
@@ -23363,15 +23297,10 @@ const isLocallyConstructedObjectMember = (reference, memberExpression) => isNode
23363
23297
  const definitionNode = definition.node;
23364
23298
  return isNodeOfType(definitionNode, "VariableDeclarator") && Boolean(definitionNode.init) && (isNodeOfType(definitionNode.init, "ObjectExpression") || isNodeOfType(definitionNode.init, "ArrayExpression"));
23365
23299
  }) === 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
- };
23370
23300
  const collectValueEvidence = (analysis, expression, frame, remainingCallFrames, visitedBindings = /* @__PURE__ */ new Set()) => {
23371
23301
  const node = stripParenExpression(expression);
23372
23302
  const evidence = emptyEvidence();
23373
- const useRefDeclarator = getUseRefDeclarator(analysis, node);
23374
- if (!useRefDeclarator && (readsPostMountValue(node) || readsPostMountValueThroughLocals(node, frame.functionNode))) {
23303
+ if (readsPostMountValue(node) || readsPostMountValueThroughLocals(node, frame.functionNode)) {
23375
23304
  evidence.readsExternalValue = true;
23376
23305
  return evidence;
23377
23306
  }
@@ -23413,11 +23342,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23413
23342
  evidence.hasUnknownSource = true;
23414
23343
  return evidence;
23415
23344
  }
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);
23345
+ if (reference.resolved.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init)) {
23421
23346
  evidence.hasUnknownSource = true;
23422
23347
  return evidence;
23423
23348
  }
@@ -23433,40 +23358,6 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23433
23358
  return collectValueEvidence(analysis, initializer.init, frame, remainingCallFrames, visitedBindings);
23434
23359
  }
23435
23360
  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
- }
23470
23361
  if (isNodeOfType(node.object, "Identifier")) {
23471
23362
  const objectReference = getRef(analysis, node.object);
23472
23363
  if (objectReference && isLocallyConstructedObjectMember(objectReference, node)) {
@@ -29245,18 +29136,6 @@ const MUTATING_COLLECTION_METHOD_NAMES = new Set([
29245
29136
  "set",
29246
29137
  "add"
29247
29138
  ]);
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
- ]);
29260
29139
  const getMemberRootBindingName = (node, scope) => {
29261
29140
  let currentNode = node;
29262
29141
  while (isNodeOfType(currentNode, "MemberExpression")) currentNode = currentNode.object;
@@ -29310,26 +29189,6 @@ const addMutatingCollectionCallDependencies = (graph, expression, scope, eventHa
29310
29189
  addDependencyNames(dependencyNames, controlDependencyNames);
29311
29190
  addDependencies(graph, receiverRootName, dependencyNames);
29312
29191
  };
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
- };
29333
29192
  const collectExpressionDependencies = (graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames) => {
29334
29193
  if (isNodeOfType(expression, "AssignmentExpression")) {
29335
29194
  addAssignmentExpressionDependencies(graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames);
@@ -29337,7 +29196,6 @@ const collectExpressionDependencies = (graph, expression, scope, eventHandlerRef
29337
29196
  }
29338
29197
  if (isNodeOfType(expression, "CallExpression")) {
29339
29198
  addMutatingCollectionCallDependencies(graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames);
29340
- addIteratorMutationDependencies(graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames);
29341
29199
  return;
29342
29200
  }
29343
29201
  if (isNodeOfType(expression, "SequenceExpression")) {
@@ -30378,26 +30236,28 @@ const noGiantComponent = defineRule({
30378
30236
  const lineCount = bodyNode.loc.end.line - bodyNode.loc.start.line + 1;
30379
30237
  return lineCount > 300 ? lineCount : null;
30380
30238
  };
30381
- const reportOversizedComponent = (nameNode, componentName) => {
30239
+ const reportOversizedComponent = (nameNode, componentName, lineCount) => {
30382
30240
  context.report({
30383
30241
  node: nameNode,
30384
- message: `Component "${componentName}" is over 300 lines long, which is hard to read & change. Split it into a few smaller components.`
30242
+ message: `Component "${componentName}" is ${lineCount} lines long, which is hard to read & change. Split it into a few smaller components.`
30385
30243
  });
30386
30244
  };
30387
30245
  return {
30388
30246
  FunctionDeclaration(node) {
30389
30247
  if (!node.id?.name || !isUppercaseName(node.id.name)) return;
30390
- if (getOversizedComponentLineCount(node) === null) return;
30248
+ const lineCount = getOversizedComponentLineCount(node);
30249
+ if (lineCount === null) return;
30391
30250
  if (!functionContainsReactRenderOutput(node, context.scopes)) return;
30392
- reportOversizedComponent(node.id, node.id.name);
30251
+ reportOversizedComponent(node.id, node.id.name, lineCount);
30393
30252
  },
30394
30253
  VariableDeclarator(node) {
30395
30254
  if (!isNodeOfType(node.id, "Identifier") || !isUppercaseName(node.id.name)) return;
30396
30255
  const functionNode = unwrapReactHocFunction(node.init);
30397
30256
  if (!functionNode) return;
30398
- if (getOversizedComponentLineCount(functionNode) === null) return;
30257
+ const lineCount = getOversizedComponentLineCount(functionNode);
30258
+ if (lineCount === null) return;
30399
30259
  if (!functionContainsReactRenderOutput(functionNode, context.scopes)) return;
30400
- reportOversizedComponent(node.id, node.id.name);
30260
+ reportOversizedComponent(node.id, node.id.name, lineCount);
30401
30261
  }
30402
30262
  };
30403
30263
  }
@@ -35168,66 +35028,6 @@ const noPropCallbackInEffect = defineRule({
35168
35028
  }
35169
35029
  });
35170
35030
  //#endregion
35171
- //#region src/plugin/rules/state-and-effects/no-prop-callback-in-render.ts
35172
- const isPreservedThroughConciseArrow = (callExpression, scopes) => {
35173
- let node = callExpression;
35174
- let parent = node.parent;
35175
- while (parent) {
35176
- if (isNodeOfType(parent, "ChainExpression")) {
35177
- node = parent;
35178
- parent = node.parent;
35179
- continue;
35180
- }
35181
- if (isNodeOfType(parent, "LogicalExpression") && parent.right === node) {
35182
- node = parent;
35183
- parent = node.parent;
35184
- continue;
35185
- }
35186
- if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === node || parent.alternate === node)) {
35187
- node = parent;
35188
- parent = node.parent;
35189
- continue;
35190
- }
35191
- if (isNodeOfType(parent, "SequenceExpression")) {
35192
- const expressions = parent.expressions ?? [];
35193
- if (expressions[expressions.length - 1] !== node) return false;
35194
- node = parent;
35195
- parent = node.parent;
35196
- continue;
35197
- }
35198
- if (!isNodeOfType(parent, "ArrowFunctionExpression") || parent.body !== node) return !isResultDiscardedCall(node);
35199
- const invocation = parent.parent;
35200
- if (!isNodeOfType(invocation, "CallExpression") || !executesDuringRender(parent, scopes)) return true;
35201
- if (invocation.arguments?.[0] === parent || invocation.arguments?.[1] === parent) {
35202
- const callee = stripParenExpression(invocation.callee);
35203
- return !(isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "forEach" && invocation.arguments[0] === parent);
35204
- }
35205
- node = invocation;
35206
- parent = node.parent;
35207
- }
35208
- return false;
35209
- };
35210
- const noPropCallbackInRender = defineRule({
35211
- id: "no-prop-callback-in-render",
35212
- title: "Prop callback invoked during render",
35213
- severity: "error",
35214
- recommendation: "Invoke the callback from the event or asynchronous operation that produced the value, or from an effect when synchronizing with an external system. Render must stay pure because React can replay or discard it.",
35215
- create: (context) => ({ CallExpression(node) {
35216
- if (!isResultDiscardedCall(node)) return;
35217
- if (isPreservedThroughConciseArrow(node, context.scopes)) return;
35218
- if (!findRenderPhaseComponentOrHook(node, context.scopes)) return;
35219
- const analysis = getProgramAnalysis(node);
35220
- if (!analysis) return;
35221
- const callee = stripParenExpression(node.callee);
35222
- if (isFunctionLike$1(callee)) return;
35223
- if (!getDownstreamRefs(analysis, callee).some((reference) => isPropCallbackInvocationRef(analysis, reference))) return;
35224
- context.report({
35225
- node,
35226
- message: "This prop callback runs during render. React can replay or discard render work, so the callback can fire more than once or for UI that never commits."
35227
- });
35228
- } })
35229
- });
35230
- //#endregion
35231
35031
  //#region src/plugin/rules/architecture/no-prop-types.ts
35232
35032
  const PROP_TYPES_PROPERTY = "propTypes";
35233
35033
  const isPropTypesKey = (key, computed) => {
@@ -57369,18 +57169,6 @@ const reactDoctorRules = [
57369
57169
  requires: [...new Set(["react", ...noPropCallbackInEffect.requires ?? []])]
57370
57170
  }
57371
57171
  },
57372
- {
57373
- key: "react-doctor/no-prop-callback-in-render",
57374
- id: "no-prop-callback-in-render",
57375
- source: "react-doctor",
57376
- originallyExternal: false,
57377
- rule: {
57378
- ...noPropCallbackInRender,
57379
- framework: "global",
57380
- category: "Bugs",
57381
- requires: [...new Set(["react", ...noPropCallbackInRender.requires ?? []])]
57382
- }
57383
- },
57384
57172
  {
57385
57173
  key: "react-doctor/no-prop-types",
57386
57174
  id: "no-prop-types",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.4-dev.6821fe0",
3
+ "version": "0.7.4-dev.7bbb792",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",