oxlint-plugin-react-doctor 0.7.4-dev.5aa82e8 → 0.7.4-dev.63fc41f

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 +20 -202
  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 = 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;
@@ -15787,40 +15721,6 @@ const isArrayVariableRenderedAsList = (declarator) => {
15787
15721
  renderedAsListCache.set(declarator, didFindRenderingUse);
15788
15722
  return didFindRenderingUse;
15789
15723
  };
15790
- const namedCallbackIteratorCallCache = /* @__PURE__ */ new WeakMap();
15791
- const findNamedCallbackIteratorCall = (functionNode) => {
15792
- const cached = namedCallbackIteratorCallCache.get(functionNode);
15793
- if (cached !== void 0) return cached;
15794
- const bindingIdentifier = getFunctionBindingIdentifier(functionNode);
15795
- if (!bindingIdentifier) {
15796
- namedCallbackIteratorCallCache.set(functionNode, null);
15797
- return null;
15798
- }
15799
- const programRoot = findProgramRoot(functionNode);
15800
- if (!programRoot) {
15801
- namedCallbackIteratorCallCache.set(functionNode, null);
15802
- return null;
15803
- }
15804
- let iteratorCall = null;
15805
- walkAst(programRoot, (node) => {
15806
- if (iteratorCall) return false;
15807
- if (!isNodeOfType(node, "Identifier") || node === bindingIdentifier || node.name !== bindingIdentifier.name) return;
15808
- const binding = findVariableInitializer(node, node.name);
15809
- if (!binding || binding.bindingIdentifier !== bindingIdentifier) return;
15810
- const callbackExpression = findTransparentExpressionRoot(node);
15811
- const callExpression = callbackExpression.parent;
15812
- if (!callExpression || !isNodeOfType(callExpression, "CallExpression")) return;
15813
- const callee = callExpression.callee;
15814
- if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || !ITERATOR_METHOD_NAMES$1.has(callee.property.name)) return;
15815
- const callbackIndex = callee.property.name === "from" ? 1 : 0;
15816
- if (callExpression.arguments[callbackIndex] !== callbackExpression) return;
15817
- if (isNonChildrenJsxAttributeValue(callExpression)) return;
15818
- iteratorCall = callExpression;
15819
- return false;
15820
- });
15821
- namedCallbackIteratorCallCache.set(functionNode, iteratorCall);
15822
- return iteratorCall;
15823
- };
15824
15724
  const findEnclosingIteratorContext = (jsxNode) => {
15825
15725
  let current = jsxNode;
15826
15726
  let isOutsideContainingFunction = false;
@@ -15835,11 +15735,6 @@ const findEnclosingIteratorContext = (jsxNode) => {
15835
15735
  const grandparent = parent.parent;
15836
15736
  if (grandparent && isNodeOfType(grandparent, "Property")) return null;
15837
15737
  if (isOutsideContainingFunction) return null;
15838
- const namedCallbackIteratorCall = findNamedCallbackIteratorCall(parent);
15839
- if (namedCallbackIteratorCall) return {
15840
- kind: "iterator",
15841
- callExpression: namedCallbackIteratorCall
15842
- };
15843
15738
  isOutsideContainingFunction = true;
15844
15739
  } else if (isNodeOfType(parent, "ArrayExpression")) {
15845
15740
  if (isOutsideContainingFunction) return null;
@@ -15883,10 +15778,7 @@ const resolveIterationItemName = (callExpression) => {
15883
15778
  if (!isNodeOfType(callee, "MemberExpression")) return null;
15884
15779
  if (!isNodeOfType(callee.property, "Identifier")) return null;
15885
15780
  const targetArgIndex = callee.property.name === "from" ? 1 : 0;
15886
- const callbackArgument = callExpression.arguments[targetArgIndex];
15887
- if (!callbackArgument) return null;
15888
- const unwrappedCallback = stripParenExpression(callbackArgument);
15889
- const callback = isNodeOfType(unwrappedCallback, "Identifier") ? findVariableInitializer(unwrappedCallback, unwrappedCallback.name)?.initializer : unwrappedCallback;
15781
+ const callback = callExpression.arguments[targetArgIndex];
15890
15782
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return null;
15891
15783
  const firstParam = callback.params[0];
15892
15784
  return firstParam && isNodeOfType(firstParam, "Identifier") ? firstParam.name : null;
@@ -22860,7 +22752,7 @@ const readsPostMountValueThroughLocals = (root, effectFn, options = {}, visitedL
22860
22752
  };
22861
22753
  //#endregion
22862
22754
  //#region src/plugin/rules/state-and-effects/utils/collect-effect-state-write-facts.ts
22863
- const SYNCHRONOUS_ITERATOR_METHOD_NAMES$1 = new Set([
22755
+ const SYNCHRONOUS_ITERATOR_METHOD_NAMES = new Set([
22864
22756
  "every",
22865
22757
  "filter",
22866
22758
  "find",
@@ -23168,7 +23060,7 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilena
23168
23060
  const calleeName = getCallCalleeName$1(child);
23169
23061
  const memberName = getStaticMemberName(callee);
23170
23062
  const isDeferringCall = calleeName !== null && DEFERRING_CALLEE_NAMES.has(calleeName) || memberName !== null && DEFERRING_MEMBER_NAMES.has(memberName);
23171
- 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");
23172
23064
  const enqueueFrame = (callableNode, argumentsForCallable, isDeferred, introducedBindings, allowWithoutInvocationEvidence = false) => {
23173
23065
  const callable = resolveWrappedCallable(analysis, callableNode);
23174
23066
  if (!callable || callable.async === true || callable === effectFunction) return;
@@ -23405,15 +23297,10 @@ const isLocallyConstructedObjectMember = (reference, memberExpression) => isNode
23405
23297
  const definitionNode = definition.node;
23406
23298
  return isNodeOfType(definitionNode, "VariableDeclarator") && Boolean(definitionNode.init) && (isNodeOfType(definitionNode.init, "ObjectExpression") || isNodeOfType(definitionNode.init, "ArrayExpression"));
23407
23299
  }) === true;
23408
- const getUseRefDeclarator = (analysis, memberExpression) => {
23409
- if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !isNodeOfType(memberExpression.object, "Identifier")) return null;
23410
- 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;
23411
- };
23412
23300
  const collectValueEvidence = (analysis, expression, frame, remainingCallFrames, visitedBindings = /* @__PURE__ */ new Set()) => {
23413
23301
  const node = stripParenExpression(expression);
23414
23302
  const evidence = emptyEvidence();
23415
- const useRefDeclarator = getUseRefDeclarator(analysis, node);
23416
- if (!useRefDeclarator && (readsPostMountValue(node) || readsPostMountValueThroughLocals(node, frame.functionNode))) {
23303
+ if (readsPostMountValue(node) || readsPostMountValueThroughLocals(node, frame.functionNode)) {
23417
23304
  evidence.readsExternalValue = true;
23418
23305
  return evidence;
23419
23306
  }
@@ -23455,11 +23342,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23455
23342
  evidence.hasUnknownSource = true;
23456
23343
  return evidence;
23457
23344
  }
23458
- const nonInitializerWrites = reference.resolved.references.filter((candidateReference) => candidateReference.isWrite() && !candidateReference.init);
23459
- if (nonInitializerWrites.length > 0) {
23460
- const writtenIdentifier = nonInitializerWrites[0]?.identifier;
23461
- const assignment = writtenIdentifier.parent;
23462
- 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)) {
23463
23346
  evidence.hasUnknownSource = true;
23464
23347
  return evidence;
23465
23348
  }
@@ -23475,40 +23358,6 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23475
23358
  return collectValueEvidence(analysis, initializer.init, frame, remainingCallFrames, visitedBindings);
23476
23359
  }
23477
23360
  if (isNodeOfType(node, "MemberExpression")) {
23478
- if (getStaticMemberName(node) === "current" && isNodeOfType(node.object, "Identifier")) {
23479
- const refBinding = getRef(analysis, node.object)?.resolved;
23480
- const refDeclarator = useRefDeclarator;
23481
- if (refBinding && refDeclarator && isNodeOfType(refDeclarator, "VariableDeclarator") && isNodeOfType(refDeclarator.init, "CallExpression")) {
23482
- if (visitedBindings.has(refBinding)) {
23483
- evidence.hasUnknownSource = true;
23484
- return evidence;
23485
- }
23486
- const refVisitedBindings = new Set(visitedBindings);
23487
- refVisitedBindings.add(refBinding);
23488
- const initialValue = refDeclarator.init.arguments?.[0];
23489
- if (initialValue) mergeEvidence(evidence, collectValueEvidence(analysis, initialValue, frame, remainingCallFrames, new Set(refVisitedBindings)));
23490
- for (const candidateReference of refBinding.references) {
23491
- if (candidateReference.init) continue;
23492
- const identifier = candidateReference.identifier;
23493
- const member = identifier.parent;
23494
- if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName(member) !== "current") {
23495
- evidence.hasUnknownSource = true;
23496
- continue;
23497
- }
23498
- const memberParent = member.parent;
23499
- if (memberParent && isNodeOfType(memberParent, "AssignmentExpression") && memberParent.left === member) {
23500
- if (memberParent.operator !== "=") {
23501
- evidence.hasUnknownSource = true;
23502
- continue;
23503
- }
23504
- mergeEvidence(evidence, collectValueEvidence(analysis, memberParent.right, frame, remainingCallFrames, new Set(refVisitedBindings)));
23505
- continue;
23506
- }
23507
- if (memberParent && isNodeOfType(memberParent, "UpdateExpression")) evidence.hasUnknownSource = true;
23508
- }
23509
- return evidence;
23510
- }
23511
- }
23512
23361
  if (isNodeOfType(node.object, "Identifier")) {
23513
23362
  const objectReference = getRef(analysis, node.object);
23514
23363
  if (objectReference && isLocallyConstructedObjectMember(objectReference, node)) {
@@ -29287,18 +29136,6 @@ const MUTATING_COLLECTION_METHOD_NAMES = new Set([
29287
29136
  "set",
29288
29137
  "add"
29289
29138
  ]);
29290
- const SYNCHRONOUS_ITERATOR_METHOD_NAMES = new Set([
29291
- "every",
29292
- "filter",
29293
- "find",
29294
- "findIndex",
29295
- "flatMap",
29296
- "forEach",
29297
- "map",
29298
- "reduce",
29299
- "reduceRight",
29300
- "some"
29301
- ]);
29302
29139
  const getMemberRootBindingName = (node, scope) => {
29303
29140
  let currentNode = node;
29304
29141
  while (isNodeOfType(currentNode, "MemberExpression")) currentNode = currentNode.object;
@@ -29352,26 +29189,6 @@ const addMutatingCollectionCallDependencies = (graph, expression, scope, eventHa
29352
29189
  addDependencyNames(dependencyNames, controlDependencyNames);
29353
29190
  addDependencies(graph, receiverRootName, dependencyNames);
29354
29191
  };
29355
- const addIteratorMutationDependencies = (graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames) => {
29356
- if (!isNodeOfType(expression, "CallExpression")) return;
29357
- if (!isNodeOfType(expression.callee, "MemberExpression")) return;
29358
- const methodName = getStaticMemberPropertyName(expression.callee);
29359
- if (!methodName || !SYNCHRONOUS_ITERATOR_METHOD_NAMES.has(methodName)) return;
29360
- const iteratorDependencyNames = collectScopedReferenceNames(expression.callee.object, scope, eventHandlerReferenceNames);
29361
- addDependencyNames(iteratorDependencyNames, controlDependencyNames);
29362
- for (const argument of expression.arguments ?? []) {
29363
- if (!isNodeOfType(argument, "ArrowFunctionExpression") && !isNodeOfType(argument, "FunctionExpression")) continue;
29364
- walkAst(argument.body, (node) => {
29365
- if (node !== argument.body && isFunctionLike$1(node)) return false;
29366
- if (!isNodeOfType(node, "CallExpression")) return;
29367
- if (!isNodeOfType(node.callee, "MemberExpression")) return;
29368
- const nestedMethodName = getStaticMemberPropertyName(node.callee);
29369
- if (!nestedMethodName || !MUTATING_COLLECTION_METHOD_NAMES.has(nestedMethodName)) return;
29370
- const receiverRootName = getMemberRootBindingName(node.callee.object, scope);
29371
- if (receiverRootName) addDependencies(graph, receiverRootName, iteratorDependencyNames);
29372
- });
29373
- }
29374
- };
29375
29192
  const collectExpressionDependencies = (graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames) => {
29376
29193
  if (isNodeOfType(expression, "AssignmentExpression")) {
29377
29194
  addAssignmentExpressionDependencies(graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames);
@@ -29379,7 +29196,6 @@ const collectExpressionDependencies = (graph, expression, scope, eventHandlerRef
29379
29196
  }
29380
29197
  if (isNodeOfType(expression, "CallExpression")) {
29381
29198
  addMutatingCollectionCallDependencies(graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames);
29382
- addIteratorMutationDependencies(graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames);
29383
29199
  return;
29384
29200
  }
29385
29201
  if (isNodeOfType(expression, "SequenceExpression")) {
@@ -30420,26 +30236,28 @@ const noGiantComponent = defineRule({
30420
30236
  const lineCount = bodyNode.loc.end.line - bodyNode.loc.start.line + 1;
30421
30237
  return lineCount > 300 ? lineCount : null;
30422
30238
  };
30423
- const reportOversizedComponent = (nameNode, componentName) => {
30239
+ const reportOversizedComponent = (nameNode, componentName, lineCount) => {
30424
30240
  context.report({
30425
30241
  node: nameNode,
30426
- 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.`
30427
30243
  });
30428
30244
  };
30429
30245
  return {
30430
30246
  FunctionDeclaration(node) {
30431
30247
  if (!node.id?.name || !isUppercaseName(node.id.name)) return;
30432
- if (getOversizedComponentLineCount(node) === null) return;
30248
+ const lineCount = getOversizedComponentLineCount(node);
30249
+ if (lineCount === null) return;
30433
30250
  if (!functionContainsReactRenderOutput(node, context.scopes)) return;
30434
- reportOversizedComponent(node.id, node.id.name);
30251
+ reportOversizedComponent(node.id, node.id.name, lineCount);
30435
30252
  },
30436
30253
  VariableDeclarator(node) {
30437
30254
  if (!isNodeOfType(node.id, "Identifier") || !isUppercaseName(node.id.name)) return;
30438
30255
  const functionNode = unwrapReactHocFunction(node.init);
30439
30256
  if (!functionNode) return;
30440
- if (getOversizedComponentLineCount(functionNode) === null) return;
30257
+ const lineCount = getOversizedComponentLineCount(functionNode);
30258
+ if (lineCount === null) return;
30441
30259
  if (!functionContainsReactRenderOutput(functionNode, context.scopes)) return;
30442
- reportOversizedComponent(node.id, node.id.name);
30260
+ reportOversizedComponent(node.id, node.id.name, lineCount);
30443
30261
  }
30444
30262
  };
30445
30263
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.4-dev.5aa82e8",
3
+ "version": "0.7.4-dev.63fc41f",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",