oxlint-plugin-react-doctor 0.7.7-dev.bdd1321 → 0.7.7-dev.c79c897

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 +433 -10
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -11003,6 +11003,14 @@ const PROMISE_CHAIN_METHOD_NAMES$1 = new Set([
11003
11003
  "catch",
11004
11004
  "finally"
11005
11005
  ]);
11006
+ const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
11007
+ const getPromiseChainCallForCallback = (candidate) => {
11008
+ let callbackContainer = candidate.parent;
11009
+ while (callbackContainer && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(callbackContainer.type)) callbackContainer = callbackContainer.parent;
11010
+ if (!isNodeOfType(callbackContainer, "CallExpression")) return null;
11011
+ if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
11012
+ return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
11013
+ };
11006
11014
  const collectEffectInvokedFunctions = (effectCallback) => {
11007
11015
  const invokedFunctions = new Set([effectCallback]);
11008
11016
  const localFunctionBindings = /* @__PURE__ */ new Map();
@@ -11014,7 +11022,6 @@ const collectEffectInvokedFunctions = (effectCallback) => {
11014
11022
  invokedFunctions.add(strippedCandidate);
11015
11023
  pendingFunctions.push(strippedCandidate);
11016
11024
  };
11017
- const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
11018
11025
  while (pendingFunctions.length > 0) {
11019
11026
  const currentFunction = pendingFunctions.pop();
11020
11027
  if (!currentFunction) break;
@@ -11844,6 +11851,94 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
11844
11851
  return didFindUnmountCleanup;
11845
11852
  };
11846
11853
  const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
11854
+ const collectBlockingBooleanStates = (expression, blockedExpressionValue, context) => {
11855
+ const unwrappedExpression = stripParenExpression(expression);
11856
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, context);
11857
+ if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
11858
+ if (!(unwrappedExpression.operator === "||" && blockedExpressionValue || unwrappedExpression.operator === "&&" && !blockedExpressionValue)) return [];
11859
+ return [...collectBlockingBooleanStates(unwrappedExpression.left, blockedExpressionValue, context), ...collectBlockingBooleanStates(unwrappedExpression.right, blockedExpressionValue, context)];
11860
+ }
11861
+ if (isNodeOfType(unwrappedExpression, "BinaryExpression") && [
11862
+ "===",
11863
+ "==",
11864
+ "!==",
11865
+ "!="
11866
+ ].includes(unwrappedExpression.operator)) {
11867
+ const leftValue = readStaticBoolean(unwrappedExpression.left);
11868
+ const rightValue = readStaticBoolean(unwrappedExpression.right);
11869
+ const booleanValue = leftValue ?? rightValue;
11870
+ const comparedKey = resolveExpressionKey(leftValue === null ? unwrappedExpression.left : unwrappedExpression.right, context);
11871
+ if (booleanValue === null || comparedKey === null) return [];
11872
+ return [{
11873
+ key: comparedKey,
11874
+ value: (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "==") === blockedExpressionValue ? booleanValue : !booleanValue
11875
+ }];
11876
+ }
11877
+ const expressionKey = resolveExpressionKey(unwrappedExpression, context);
11878
+ return expressionKey === null ? [] : [{
11879
+ key: expressionKey,
11880
+ value: blockedExpressionValue
11881
+ }];
11882
+ };
11883
+ const isDirectEarlyReturnConsequent = (ifStatement) => {
11884
+ if (!isNodeOfType(ifStatement, "IfStatement") || ifStatement.alternate) return false;
11885
+ if (isNodeOfType(ifStatement.consequent, "ReturnStatement")) return true;
11886
+ return isNodeOfType(ifStatement.consequent, "BlockStatement") && ifStatement.consequent.body.length === 1 && isNodeOfType(ifStatement.consequent.body[0], "ReturnStatement");
11887
+ };
11888
+ const collectDeferredUsageGuardStates = (callback, usageNode, context) => {
11889
+ if (!isFunctionLike$2(callback) || callback.async) return [];
11890
+ const guardStates = [];
11891
+ walkAst(callback.body, (child) => {
11892
+ if (child !== callback.body && isFunctionLike$2(child)) return false;
11893
+ if (isNodeOfType(child, "IfStatement") && isDirectEarlyReturnConsequent(child) && doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], callback, context)) guardStates.push(...collectBlockingBooleanStates(child.test, true, context));
11894
+ });
11895
+ let descendant = usageNode;
11896
+ let ancestor = descendant.parent;
11897
+ while (ancestor && ancestor !== callback) {
11898
+ if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant) guardStates.push(...collectBlockingBooleanStates(ancestor.test, false, context));
11899
+ descendant = ancestor;
11900
+ ancestor = ancestor.parent;
11901
+ }
11902
+ return guardStates;
11903
+ };
11904
+ const cleanupReturnInvalidatesGuard = (cleanupReturn, guardState, context) => {
11905
+ if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
11906
+ const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
11907
+ if (!cleanupFunction || !isFunctionLike$2(cleanupFunction) || cleanupFunction.async) return false;
11908
+ let didInvalidateGuard = false;
11909
+ walkAst(cleanupFunction.body, (child) => {
11910
+ if (didInvalidateGuard) return false;
11911
+ if (child !== cleanupFunction.body && isFunctionLike$2(child)) return false;
11912
+ if (isNodeOfType(child, "AssignmentExpression") && child.operator === "=" && resolveExpressionKey(child.left, context) === guardState.key && readStaticBoolean(child.right) === guardState.value && context.cfg.isUnconditionalFromEntry(child)) {
11913
+ didInvalidateGuard = true;
11914
+ return false;
11915
+ }
11916
+ });
11917
+ return didInvalidateGuard;
11918
+ };
11919
+ const deferredUsageWritesGuardBeforeUsage = (callback, usageNode, guardState, context) => {
11920
+ const usageStart = getRangeStart(usageNode);
11921
+ if (!isFunctionLike$2(callback) || usageStart === null) return true;
11922
+ let didWriteGuard = false;
11923
+ walkAst(callback.body, (child) => {
11924
+ if (didWriteGuard) return false;
11925
+ if (child !== callback.body && isFunctionLike$2(child)) return false;
11926
+ const childStart = getRangeStart(child);
11927
+ if (childStart === null || childStart >= usageStart) return;
11928
+ const writtenExpression = isNodeOfType(child, "AssignmentExpression") ? child.left : isNodeOfType(child, "UpdateExpression") ? child.argument : null;
11929
+ if (writtenExpression && resolveExpressionKey(writtenExpression, context) === guardState.key) {
11930
+ didWriteGuard = true;
11931
+ return false;
11932
+ }
11933
+ });
11934
+ return didWriteGuard;
11935
+ };
11936
+ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
11937
+ const usageFunction = findEnclosingFunction(usage.node);
11938
+ const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
11939
+ if (usage.handleKey === null || !usageFunction || usageFunction === callback || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
11940
+ return collectDeferredUsageGuardStates(usageFunction, usage.node, context).some((guardState) => !deferredUsageWritesGuardBeforeUsage(usageFunction, usage.node, guardState, context) && cleanupReturns.every((cleanupReturn) => cleanupReturnInvalidatesGuard(cleanupReturn, guardState, context)));
11941
+ };
11847
11942
  const effectHasCleanupForUsage = (callback, usage, context) => {
11848
11943
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
11849
11944
  if (callback.async) return false;
@@ -11883,6 +11978,7 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
11883
11978
  if (!cleanupFunction || !isFunctionLike$2(cleanupFunction)) return;
11884
11979
  if (doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context)) matchingCleanupReturns.push(child);
11885
11980
  });
11981
+ if (hasGuardedDeferredCleanup(callback, usage, matchingCleanupReturns, context)) return true;
11886
11982
  return doMatchingNodesCoverEveryPathAfterUsage(resolveCleanupPathAnchor(usage.node, callback, context), matchingCleanupReturns, context);
11887
11983
  };
11888
11984
  const findFirstUsageWithoutCleanup = (callback, usages, context) => {
@@ -18333,6 +18429,26 @@ const MEMBERSHIP_COMPARISON_OPERATORS = new Set([
18333
18429
  ]);
18334
18430
  const isNegativeOneLiteral = (expression) => Boolean(expression) && isNodeOfType(expression, "UnaryExpression") && expression.operator === "-" && isNodeOfType(expression.argument, "Literal") && expression.argument.value === 1;
18335
18431
  const isZeroLiteral = (expression) => Boolean(expression) && isNodeOfType(expression, "Literal") && expression.value === 0;
18432
+ const isToIntegerOrInfinityZero = (value) => Number.isNaN(value) || Math.trunc(value) === 0;
18433
+ const isZeroFromIndex = (expression) => {
18434
+ if (!expression) return false;
18435
+ const strippedExpression = stripParenExpression(expression);
18436
+ if (isNodeOfType(strippedExpression, "Literal")) {
18437
+ if (typeof strippedExpression.value === "number") return isToIntegerOrInfinityZero(strippedExpression.value);
18438
+ if (typeof strippedExpression.value === "string") return isToIntegerOrInfinityZero(Number(strippedExpression.value));
18439
+ return strippedExpression.value === null || strippedExpression.value === false;
18440
+ }
18441
+ if (isNodeOfType(strippedExpression, "UnaryExpression") && strippedExpression.operator === "void") return true;
18442
+ if (isNodeOfType(strippedExpression, "UnaryExpression") && (strippedExpression.operator === "-" || strippedExpression.operator === "+") && isNodeOfType(strippedExpression.argument, "Literal") && typeof strippedExpression.argument.value === "number") return isToIntegerOrInfinityZero(strippedExpression.operator === "-" ? -strippedExpression.argument.value : strippedExpression.argument.value);
18443
+ if (isNodeOfType(strippedExpression, "Identifier")) return (strippedExpression.name === "undefined" || strippedExpression.name === "NaN") && findVariableInitializer(strippedExpression, strippedExpression.name) === null;
18444
+ return isNodeOfType(strippedExpression, "MemberExpression") && !strippedExpression.computed && isNodeOfType(strippedExpression.object, "Identifier") && strippedExpression.object.name === "Number" && isNodeOfType(strippedExpression.property, "Identifier") && strippedExpression.property.name === "NaN" && findVariableInitializer(strippedExpression.object, strippedExpression.object.name) === null;
18445
+ };
18446
+ const hasSemanticsPreservingIncludesArguments = (node) => {
18447
+ if (node.arguments.length === 1) return !isNodeOfType(node.arguments[0], "SpreadElement");
18448
+ if (node.arguments.length !== 2) return false;
18449
+ if (isNodeOfType(node.arguments[0], "SpreadElement") || isNodeOfType(node.arguments[1], "SpreadElement")) return false;
18450
+ return isZeroFromIndex(node.arguments[1]);
18451
+ };
18336
18452
  const PARENT_WRAPPER_TYPES = new Set([
18337
18453
  "ParenthesizedExpression",
18338
18454
  "ChainExpression",
@@ -18353,6 +18469,306 @@ const isIndexOfResultUsedAsMembershipTest = (node) => {
18353
18469
  if (isNegativeOneLiteral(otherOperand)) return true;
18354
18470
  return isZeroLiteral(otherOperand) && (parent.operator === ">=" || parent.operator === "<");
18355
18471
  };
18472
+ const getTypeAnnotation = (node) => {
18473
+ if (!node || !("typeAnnotation" in node)) return null;
18474
+ const annotation = node.typeAnnotation;
18475
+ if (!annotation || !isNodeOfType(annotation, "TSTypeAnnotation")) return null;
18476
+ return annotation.typeAnnotation;
18477
+ };
18478
+ const getDeclaredPropertyType = (members, propertyName) => {
18479
+ for (const member of members) if (isNodeOfType(member, "TSPropertySignature") && isNodeOfType(member.key, "Identifier") && member.key.name === propertyName) return getTypeAnnotation(member);
18480
+ return null;
18481
+ };
18482
+ const getArrayElementType = (typeNode) => {
18483
+ if (!typeNode) return null;
18484
+ if (isNodeOfType(typeNode, "TSArrayType")) return typeNode.elementType;
18485
+ if (isNodeOfType(typeNode, "TSTypeReference") && isNodeOfType(typeNode.typeName, "Identifier") && (typeNode.typeName.name === "Array" || typeNode.typeName.name === "ReadonlyArray")) return typeNode.typeArguments?.params?.[0] ?? null;
18486
+ if (isNodeOfType(typeNode, "TSUnionType")) {
18487
+ const arrayElementTypes = typeNode.types.map(getArrayElementType).filter(Boolean);
18488
+ return arrayElementTypes.length === 1 ? arrayElementTypes[0] : null;
18489
+ }
18490
+ return null;
18491
+ };
18492
+ const getDestructuredDeclaredType = (identifier) => {
18493
+ const binding = findVariableInitializer(identifier, identifier.name);
18494
+ if (!binding) return null;
18495
+ const property = binding.bindingIdentifier.parent;
18496
+ const objectPattern = property?.parent;
18497
+ if (!isNodeOfType(property, "Property") || !isNodeOfType(property.key, "Identifier") || !isNodeOfType(objectPattern, "ObjectPattern")) return null;
18498
+ const propsType = getTypeAnnotation(objectPattern);
18499
+ if (!propsType) return null;
18500
+ if (isNodeOfType(propsType, "TSTypeLiteral")) return getDeclaredPropertyType(propsType.members ?? [], property.key.name);
18501
+ if (!isNodeOfType(propsType, "TSTypeReference") || !isNodeOfType(propsType.typeName, "Identifier")) return null;
18502
+ const program = findProgramRoot(identifier);
18503
+ if (!program) return null;
18504
+ for (const statement of program.body) {
18505
+ const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
18506
+ if (isNodeOfType(declaration, "TSInterfaceDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === propsType.typeName.name) return getDeclaredPropertyType(declaration.body.body, property.key.name);
18507
+ if (isNodeOfType(declaration, "TSTypeAliasDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === propsType.typeName.name && isNodeOfType(declaration.typeAnnotation, "TSTypeLiteral")) return getDeclaredPropertyType(declaration.typeAnnotation.members ?? [], property.key.name);
18508
+ }
18509
+ return null;
18510
+ };
18511
+ const getIdentifierDeclaredType = (identifier, visitedBindingIdentifiers = /* @__PURE__ */ new Set()) => {
18512
+ const binding = findVariableInitializer(identifier, identifier.name);
18513
+ if (!binding || visitedBindingIdentifiers.has(binding.bindingIdentifier)) return null;
18514
+ visitedBindingIdentifiers.add(binding.bindingIdentifier);
18515
+ const directType = getTypeAnnotation(binding.bindingIdentifier);
18516
+ if (directType) return directType;
18517
+ const initializer = binding.initializer;
18518
+ if (isNodeOfType(initializer, "TSAsExpression") || isNodeOfType(initializer, "TSTypeAssertion") || isNodeOfType(initializer, "TSSatisfiesExpression")) return initializer.typeAnnotation;
18519
+ const destructuredType = getDestructuredDeclaredType(identifier);
18520
+ if (destructuredType) return destructuredType;
18521
+ const declarator = binding.bindingIdentifier.parent;
18522
+ const declaration = declarator?.parent;
18523
+ const forOfStatement = declaration?.parent;
18524
+ if (isNodeOfType(declarator, "VariableDeclarator") && isNodeOfType(declaration, "VariableDeclaration") && isNodeOfType(forOfStatement, "ForOfStatement") && forOfStatement.left === declaration && isNodeOfType(forOfStatement.right, "Identifier")) return getArrayElementType(getIdentifierDeclaredType(forOfStatement.right, visitedBindingIdentifiers));
18525
+ return null;
18526
+ };
18527
+ const isNativeIterationIndex = (identifier) => {
18528
+ const binding = findVariableInitializer(identifier, identifier.name);
18529
+ if (!binding) return false;
18530
+ const callback = binding.bindingIdentifier.parent;
18531
+ if (!isInlineFunctionExpression(callback)) return false;
18532
+ const callbackCall = callback.parent;
18533
+ if (!isNodeOfType(callbackCall, "CallExpression") || !isIterationCallbackCall(callbackCall) || !isNodeOfType(callbackCall.callee, "MemberExpression") || !isNodeOfType(callbackCall.callee.property, "Identifier")) return false;
18534
+ const indexParameterPosition = callbackCall.callee.property.name === "reduce" || callbackCall.callee.property.name === "reduceRight" ? 2 : 1;
18535
+ return callback.params?.[indexParameterPosition] === binding.bindingIdentifier;
18536
+ };
18537
+ const hasSameIdentifierBinding = (leftIdentifier, rightIdentifier) => {
18538
+ if (leftIdentifier.name !== rightIdentifier.name) return false;
18539
+ const leftBinding = findVariableInitializer(leftIdentifier, leftIdentifier.name);
18540
+ const rightBinding = findVariableInitializer(rightIdentifier, rightIdentifier.name);
18541
+ return Boolean(leftBinding && rightBinding && leftBinding.bindingIdentifier === rightBinding.bindingIdentifier);
18542
+ };
18543
+ const NON_NAN_RELATIONAL_OPERATORS = new Set([
18544
+ "<",
18545
+ "<=",
18546
+ ">",
18547
+ ">="
18548
+ ]);
18549
+ const testProvesIdentifierIsNotNaN = (test, identifier) => {
18550
+ if (!test) return false;
18551
+ const strippedTest = stripParenExpression(test);
18552
+ if (isNodeOfType(strippedTest, "LogicalExpression") && strippedTest.operator === "&&") return testProvesIdentifierIsNotNaN(strippedTest.left, identifier) || testProvesIdentifierIsNotNaN(strippedTest.right, identifier);
18553
+ if (!isNodeOfType(strippedTest, "BinaryExpression") || !NON_NAN_RELATIONAL_OPERATORS.has(strippedTest.operator)) return false;
18554
+ return isNodeOfType(strippedTest.left, "Identifier") && hasSameIdentifierBinding(strippedTest.left, identifier) || isNodeOfType(strippedTest.right, "Identifier") && hasSameIdentifierBinding(strippedTest.right, identifier);
18555
+ };
18556
+ const writeTargetContainsIdentifierBinding = (writeTarget, identifier) => {
18557
+ let containsBinding = false;
18558
+ walkAst(writeTarget, (child) => {
18559
+ if (isNodeOfType(child, "Identifier") && hasSameIdentifierBinding(child, identifier)) {
18560
+ containsBinding = true;
18561
+ return false;
18562
+ }
18563
+ });
18564
+ return containsBinding;
18565
+ };
18566
+ const hasWriteBeforeQuery = (body, identifier) => {
18567
+ const queryStart = getRangeStart(identifier);
18568
+ if (queryStart === null) return true;
18569
+ let hasEarlierWrite = false;
18570
+ walkAst(body, (child) => {
18571
+ if (hasEarlierWrite) return false;
18572
+ const childStart = getRangeStart(child);
18573
+ if (childStart !== null && childStart >= queryStart) return false;
18574
+ if (child !== body && isFunctionLike$2(child)) return false;
18575
+ const writeTarget = isNodeOfType(child, "AssignmentExpression") ? child.left : isNodeOfType(child, "UpdateExpression") ? child.argument : isNodeOfType(child, "ForInStatement") || isNodeOfType(child, "ForOfStatement") ? child.left : null;
18576
+ if (writeTarget && writeTargetContainsIdentifierBinding(writeTarget, identifier)) {
18577
+ hasEarlierWrite = true;
18578
+ return false;
18579
+ }
18580
+ });
18581
+ return hasEarlierWrite;
18582
+ };
18583
+ const isProtectedByRelationalLoopGuard = (identifier) => {
18584
+ let descendant = identifier;
18585
+ let ancestor = identifier.parent;
18586
+ while (ancestor) {
18587
+ if (isFunctionLike$2(ancestor)) return false;
18588
+ if ((isNodeOfType(ancestor, "ForStatement") || isNodeOfType(ancestor, "WhileStatement")) && ancestor.body === descendant && testProvesIdentifierIsNotNaN(ancestor.test, identifier)) return !hasWriteBeforeQuery(ancestor.body, identifier);
18589
+ descendant = ancestor;
18590
+ ancestor = ancestor.parent;
18591
+ }
18592
+ return false;
18593
+ };
18594
+ const isKnownSafeIndexOfQuery = (query) => {
18595
+ if (!query) return false;
18596
+ const strippedQuery = stripParenExpression(query);
18597
+ if (isNodeOfType(strippedQuery, "Literal")) return typeof strippedQuery.value !== "number" || Number.isFinite(strippedQuery.value);
18598
+ if (!isNodeOfType(strippedQuery, "Identifier")) return false;
18599
+ if (isNativeIterationIndex(strippedQuery)) return true;
18600
+ return isProtectedByRelationalLoopGuard(strippedQuery);
18601
+ };
18602
+ const findSameFileTypeAlias = (reference, typeName) => {
18603
+ const program = findProgramRoot(reference);
18604
+ if (!program) return null;
18605
+ for (const statement of program.body) {
18606
+ const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
18607
+ if (declaration && isNodeOfType(declaration, "TSTypeAliasDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === typeName) return declaration;
18608
+ }
18609
+ return null;
18610
+ };
18611
+ const hasDeclaredMembershipMethod = (members, methodName) => members.some((member) => (isNodeOfType(member, "TSMethodSignature") || isNodeOfType(member, "TSPropertySignature")) && isNodeOfType(member.key, "Identifier") && member.key.name === methodName);
18612
+ const isKnownUserlandMembershipReceiver = (receiver, methodName) => {
18613
+ if (!isNodeOfType(receiver, "Identifier")) return false;
18614
+ const declaredType = getIdentifierDeclaredType(receiver);
18615
+ if (!declaredType) return false;
18616
+ if (isNodeOfType(declaredType, "TSTypeLiteral")) return hasDeclaredMembershipMethod(declaredType.members ?? [], methodName);
18617
+ if (!isNodeOfType(declaredType, "TSTypeReference") || !isNodeOfType(declaredType.typeName, "Identifier")) return false;
18618
+ const typeAlias = findSameFileTypeAlias(receiver, declaredType.typeName.name);
18619
+ if (typeAlias && isNodeOfType(typeAlias.typeAnnotation, "TSTypeLiteral")) return hasDeclaredMembershipMethod(typeAlias.typeAnnotation.members ?? [], methodName);
18620
+ const program = findProgramRoot(receiver);
18621
+ if (!program) return false;
18622
+ for (const statement of program.body) {
18623
+ const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
18624
+ if (declaration && isNodeOfType(declaration, "TSInterfaceDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === declaredType.typeName.name) return hasDeclaredMembershipMethod(declaration.body.body, methodName);
18625
+ }
18626
+ return false;
18627
+ };
18628
+ const findTypeParameter = (reference, typeName) => {
18629
+ let ancestor = reference.parent;
18630
+ while (ancestor) {
18631
+ if (isFunctionLike$2(ancestor) || isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) {
18632
+ const matchingTypeParameter = ancestor.typeParameters?.params?.find((typeParameter) => isNodeOfType(typeParameter, "TSTypeParameter") && isNodeOfType(typeParameter.name, "Identifier") && typeParameter.name.name === typeName);
18633
+ if (matchingTypeParameter && isNodeOfType(matchingTypeParameter, "TSTypeParameter")) return matchingTypeParameter;
18634
+ }
18635
+ ancestor = ancestor.parent;
18636
+ }
18637
+ return null;
18638
+ };
18639
+ const POSSIBLY_NUMERIC_TYPE_REFERENCE_NAMES = new Set(["NonNullable", "PropertyKey"]);
18640
+ const buildTypeAliasArguments = (typeAlias, typeReference, inheritedArguments) => {
18641
+ const typeArguments = new Map(inheritedArguments);
18642
+ for (const [index, typeParameter] of (typeAlias.typeParameters?.params ?? []).entries()) {
18643
+ if (!isNodeOfType(typeParameter, "TSTypeParameter")) continue;
18644
+ if (!isNodeOfType(typeParameter.name, "Identifier")) continue;
18645
+ const argument = typeReference.typeArguments?.params?.[index] ?? typeParameter.default;
18646
+ if (argument) typeArguments.set(typeParameter.name.name, argument);
18647
+ }
18648
+ return typeArguments;
18649
+ };
18650
+ const typeCanHaveSameValueZeroDifference = (typeNode, reference, activeTypeNodes, typeArguments = /* @__PURE__ */ new Map()) => {
18651
+ if (!typeNode) return false;
18652
+ if (isNodeOfType(typeNode, "TSNumberKeyword") || isNodeOfType(typeNode, "TSAnyKeyword") || isNodeOfType(typeNode, "TSUnknownKeyword")) return true;
18653
+ if (isNodeOfType(typeNode, "TSStringKeyword") || isNodeOfType(typeNode, "TSBooleanKeyword") || isNodeOfType(typeNode, "TSBigIntKeyword") || isNodeOfType(typeNode, "TSSymbolKeyword") || isNodeOfType(typeNode, "TSNullKeyword") || isNodeOfType(typeNode, "TSUndefinedKeyword") || isNodeOfType(typeNode, "TSObjectKeyword") || isNodeOfType(typeNode, "TSLiteralType") || isNodeOfType(typeNode, "TSFunctionType")) return false;
18654
+ if (isNodeOfType(typeNode, "TSTypeLiteral")) return (typeNode.members?.length ?? 0) === 0;
18655
+ if (isNodeOfType(typeNode, "TSTypeOperator") && typeNode.operator === "keyof") return false;
18656
+ if (isNodeOfType(typeNode, "TSUnionType") || isNodeOfType(typeNode, "TSIntersectionType")) return typeNode.types.some((memberType) => typeCanHaveSameValueZeroDifference(memberType, reference, activeTypeNodes, typeArguments));
18657
+ if (isNodeOfType(typeNode, "TSOptionalType") || isNodeOfType(typeNode, "TSRestType")) return typeCanHaveSameValueZeroDifference(typeNode.typeAnnotation, reference, activeTypeNodes, typeArguments);
18658
+ if (isNodeOfType(typeNode, "TSNamedTupleMember")) return typeCanHaveSameValueZeroDifference(typeNode.elementType, reference, activeTypeNodes, typeArguments);
18659
+ if (!isNodeOfType(typeNode, "TSTypeReference")) return true;
18660
+ if (!isNodeOfType(typeNode.typeName, "Identifier")) return false;
18661
+ const substitutedType = typeArguments.get(typeNode.typeName.name);
18662
+ if (substitutedType) {
18663
+ const remainingArguments = new Map(typeArguments);
18664
+ remainingArguments.delete(typeNode.typeName.name);
18665
+ return typeCanHaveSameValueZeroDifference(substitutedType, reference, activeTypeNodes, remainingArguments);
18666
+ }
18667
+ const typeParameter = findTypeParameter(reference, typeNode.typeName.name);
18668
+ if (typeParameter) {
18669
+ if (!typeParameter.constraint || activeTypeNodes.has(typeParameter)) return true;
18670
+ activeTypeNodes.add(typeParameter);
18671
+ const constraintCanDiffer = typeCanHaveSameValueZeroDifference(typeParameter.constraint, reference, activeTypeNodes, typeArguments);
18672
+ activeTypeNodes.delete(typeParameter);
18673
+ return constraintCanDiffer;
18674
+ }
18675
+ const typeAlias = findSameFileTypeAlias(reference, typeNode.typeName.name);
18676
+ if (!typeAlias) return POSSIBLY_NUMERIC_TYPE_REFERENCE_NAMES.has(typeNode.typeName.name) || /(?:number|numeric)/i.test(typeNode.typeName.name);
18677
+ if (activeTypeNodes.has(typeAlias)) return true;
18678
+ activeTypeNodes.add(typeAlias);
18679
+ const canDiffer = typeCanHaveSameValueZeroDifference(typeAlias.typeAnnotation, reference, activeTypeNodes, buildTypeAliasArguments(typeAlias, typeNode, typeArguments));
18680
+ activeTypeNodes.delete(typeAlias);
18681
+ return canDiffer;
18682
+ };
18683
+ const arrayTypeCanHaveSameValueZeroDifference = (typeNode, reference, activeTypeNodes, typeArguments = /* @__PURE__ */ new Map()) => {
18684
+ if (!typeNode) return false;
18685
+ if (isNodeOfType(typeNode, "TSArrayType")) return typeCanHaveSameValueZeroDifference(typeNode.elementType, reference, activeTypeNodes, typeArguments);
18686
+ if (isNodeOfType(typeNode, "TSTupleType")) return typeNode.elementTypes.some((elementType) => {
18687
+ if (isNodeOfType(elementType, "TSRestType")) return arrayTypeCanHaveSameValueZeroDifference(elementType.typeAnnotation, reference, activeTypeNodes, typeArguments);
18688
+ if (isNodeOfType(elementType, "TSNamedTupleMember")) return typeCanHaveSameValueZeroDifference(elementType.elementType, reference, activeTypeNodes, typeArguments);
18689
+ return typeCanHaveSameValueZeroDifference(elementType, reference, activeTypeNodes, typeArguments);
18690
+ });
18691
+ if (isNodeOfType(typeNode, "TSTypeOperator")) return arrayTypeCanHaveSameValueZeroDifference(typeNode.typeAnnotation ?? null, reference, activeTypeNodes, typeArguments);
18692
+ if (isNodeOfType(typeNode, "TSUnionType") || isNodeOfType(typeNode, "TSIntersectionType")) return typeNode.types.some((memberType) => arrayTypeCanHaveSameValueZeroDifference(memberType, reference, activeTypeNodes, typeArguments));
18693
+ if (!isNodeOfType(typeNode, "TSTypeReference") || !isNodeOfType(typeNode.typeName, "Identifier")) return false;
18694
+ const substitutedType = typeArguments.get(typeNode.typeName.name);
18695
+ if (substitutedType) {
18696
+ const remainingArguments = new Map(typeArguments);
18697
+ remainingArguments.delete(typeNode.typeName.name);
18698
+ return arrayTypeCanHaveSameValueZeroDifference(substitutedType, reference, activeTypeNodes, remainingArguments);
18699
+ }
18700
+ if (typeNode.typeName.name === "Array" || typeNode.typeName.name === "ReadonlyArray") return typeCanHaveSameValueZeroDifference(typeNode.typeArguments?.params?.[0] ?? null, reference, activeTypeNodes, typeArguments);
18701
+ const typeAlias = findSameFileTypeAlias(reference, typeNode.typeName.name);
18702
+ if (!typeAlias || activeTypeNodes.has(typeAlias)) return false;
18703
+ activeTypeNodes.add(typeAlias);
18704
+ const canDiffer = arrayTypeCanHaveSameValueZeroDifference(typeAlias.typeAnnotation, reference, activeTypeNodes, buildTypeAliasArguments(typeAlias, typeNode, typeArguments));
18705
+ activeTypeNodes.delete(typeAlias);
18706
+ return canDiffer;
18707
+ };
18708
+ const isKnownArrayType = (typeNode, reference, activeTypeNodes, typeArguments = /* @__PURE__ */ new Map()) => {
18709
+ if (!typeNode) return false;
18710
+ if (isNodeOfType(typeNode, "TSArrayType") || isNodeOfType(typeNode, "TSTupleType")) return true;
18711
+ if (isNodeOfType(typeNode, "TSTypeOperator")) return isKnownArrayType(typeNode.typeAnnotation ?? null, reference, activeTypeNodes, typeArguments);
18712
+ if (isNodeOfType(typeNode, "TSUnionType")) {
18713
+ const nonNullishTypes = typeNode.types.filter((memberType) => !isNodeOfType(memberType, "TSNullKeyword") && !isNodeOfType(memberType, "TSUndefinedKeyword"));
18714
+ return nonNullishTypes.length > 0 && nonNullishTypes.every((memberType) => isKnownArrayType(memberType, reference, activeTypeNodes, typeArguments));
18715
+ }
18716
+ if (isNodeOfType(typeNode, "TSIntersectionType")) return typeNode.types.some((memberType) => isKnownArrayType(memberType, reference, activeTypeNodes, typeArguments));
18717
+ if (!isNodeOfType(typeNode, "TSTypeReference") || !isNodeOfType(typeNode.typeName, "Identifier")) return false;
18718
+ const substitutedType = typeArguments.get(typeNode.typeName.name);
18719
+ if (substitutedType) {
18720
+ const remainingArguments = new Map(typeArguments);
18721
+ remainingArguments.delete(typeNode.typeName.name);
18722
+ return isKnownArrayType(substitutedType, reference, activeTypeNodes, remainingArguments);
18723
+ }
18724
+ if (typeNode.typeName.name === "Array" || typeNode.typeName.name === "ReadonlyArray") return true;
18725
+ const typeParameter = findTypeParameter(reference, typeNode.typeName.name);
18726
+ if (typeParameter?.constraint) {
18727
+ if (activeTypeNodes.has(typeParameter)) return false;
18728
+ activeTypeNodes.add(typeParameter);
18729
+ const isConstraintArray = isKnownArrayType(typeParameter.constraint, reference, activeTypeNodes, typeArguments);
18730
+ activeTypeNodes.delete(typeParameter);
18731
+ return isConstraintArray;
18732
+ }
18733
+ const typeAlias = findSameFileTypeAlias(reference, typeNode.typeName.name);
18734
+ if (!typeAlias || activeTypeNodes.has(typeAlias)) return false;
18735
+ activeTypeNodes.add(typeAlias);
18736
+ const isArray = isKnownArrayType(typeAlias.typeAnnotation, reference, activeTypeNodes, buildTypeAliasArguments(typeAlias, typeNode, typeArguments));
18737
+ activeTypeNodes.delete(typeAlias);
18738
+ return isArray;
18739
+ };
18740
+ const isKnownNativeArrayReceiver = (receiver) => {
18741
+ if (isNodeOfType(receiver, "ArrayExpression")) return true;
18742
+ if (isNodeOfType(receiver, "Identifier") && isKnownArrayType(getIdentifierDeclaredType(receiver), receiver, /* @__PURE__ */ new Set())) return true;
18743
+ const initializer = getResolvedInitializer(receiver)?.initializer;
18744
+ if (!initializer) return false;
18745
+ const strippedInitializer = stripParenExpression(initializer);
18746
+ if (isNodeOfType(strippedInitializer, "ArrayExpression")) return true;
18747
+ if (isNodeOfType(strippedInitializer, "NewExpression") && isNodeOfType(strippedInitializer.callee, "Identifier") && strippedInitializer.callee.name === "Array" && findVariableInitializer(strippedInitializer.callee, strippedInitializer.callee.name) === null) return true;
18748
+ return isNodeOfType(strippedInitializer, "CallExpression") && isNodeOfType(strippedInitializer.callee, "MemberExpression") && isNodeOfType(strippedInitializer.callee.object, "Identifier") && strippedInitializer.callee.object.name === "Array" && isNodeOfType(strippedInitializer.callee.property, "Identifier") && (strippedInitializer.callee.property.name === "from" || strippedInitializer.callee.property.name === "of") && findVariableInitializer(strippedInitializer.callee.object, strippedInitializer.callee.object.name) === null;
18749
+ };
18750
+ const isKnownDenseArrayReceiver = (receiver) => {
18751
+ const initializer = isNodeOfType(receiver, "Identifier") ? getResolvedInitializer(receiver)?.initializer : receiver;
18752
+ if (!initializer) return false;
18753
+ const strippedInitializer = stripParenExpression(initializer);
18754
+ if (isNodeOfType(strippedInitializer, "ArrayExpression")) return (strippedInitializer.elements ?? []).every((element) => element !== null && !isNodeOfType(element, "SpreadElement"));
18755
+ return isNodeOfType(strippedInitializer, "CallExpression") && isNodeOfType(strippedInitializer.callee, "MemberExpression") && isNodeOfType(strippedInitializer.callee.object, "Identifier") && strippedInitializer.callee.object.name === "Array" && isNodeOfType(strippedInitializer.callee.property, "Identifier") && (strippedInitializer.callee.property.name === "from" || strippedInitializer.callee.property.name === "of") && findVariableInitializer(strippedInitializer.callee.object, strippedInitializer.callee.object.name) === null;
18756
+ };
18757
+ const isKnownUnsafeIndexOfQuery = (query, receiver) => {
18758
+ if (!query) return true;
18759
+ const strippedQuery = stripParenExpression(query);
18760
+ if (isNodeOfType(strippedQuery, "Identifier")) {
18761
+ if (strippedQuery.name === "undefined" && findVariableInitializer(strippedQuery, strippedQuery.name) === null) return !isKnownDenseArrayReceiver(receiver);
18762
+ if (strippedQuery.name === "NaN" && findVariableInitializer(strippedQuery, strippedQuery.name) === null) return true;
18763
+ return typeCanHaveSameValueZeroDifference(getIdentifierDeclaredType(strippedQuery), strippedQuery, /* @__PURE__ */ new Set());
18764
+ }
18765
+ if (isNodeOfType(strippedQuery, "MemberExpression")) return !strippedQuery.computed && isNodeOfType(strippedQuery.object, "Identifier") && strippedQuery.object.name === "Number" && isNodeOfType(strippedQuery.property, "Identifier") && strippedQuery.property.name === "NaN" && findVariableInitializer(strippedQuery.object, strippedQuery.object.name) === null;
18766
+ return true;
18767
+ };
18768
+ const isKnownUnsafeIndexOfReceiver = (receiver) => {
18769
+ if (!isNodeOfType(receiver, "Identifier")) return false;
18770
+ return arrayTypeCanHaveSameValueZeroDifference(getIdentifierDeclaredType(receiver), receiver, /* @__PURE__ */ new Set());
18771
+ };
18356
18772
  const ITERATION_CALLBACK_METHOD_NAMES = new Set([
18357
18773
  "forEach",
18358
18774
  "map",
@@ -18481,16 +18897,22 @@ const jsSetMapLookups = defineRule({
18481
18897
  if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier")) return;
18482
18898
  const methodName = node.callee.property.name;
18483
18899
  if (methodName !== "includes" && methodName !== "indexOf") return;
18484
- if (methodName === "indexOf" && !isIndexOfResultUsedAsMembershipTest(node)) return;
18900
+ if (methodName === "includes" && !hasSemanticsPreservingIncludesArguments(node)) return;
18901
+ if (methodName === "indexOf" && (node.arguments.length !== 1 || isNodeOfType(node.arguments[0], "SpreadElement") || !isIndexOfResultUsedAsMembershipTest(node))) return;
18485
18902
  const rawReceiver = node.callee.object;
18486
18903
  if (!rawReceiver) return;
18487
18904
  const receiver = stripParenExpression(rawReceiver);
18905
+ const isKnownNativeArray = isKnownNativeArrayReceiver(receiver);
18906
+ if (isKnownUserlandMembershipReceiver(receiver, methodName)) return;
18907
+ if (methodName === "includes" && node.arguments.length === 2 && !isKnownNativeArray) return;
18908
+ const query = node.arguments[0];
18909
+ if (methodName === "indexOf" && !isKnownSafeIndexOfQuery(query) && (isKnownUnsafeIndexOfQuery(query, receiver) || isKnownUnsafeIndexOfReceiver(receiver))) return;
18488
18910
  if (isLikelyStringReceiver(receiver)) return;
18489
18911
  if (isSmallInlineLiteralArray(receiver)) return;
18490
18912
  if (isScreamingSnakeCaseConstantReceiver(receiver)) return;
18491
18913
  if (isSmallFixedListMember(receiver)) return;
18492
- if (isSubstringSearchLiteral(node.arguments?.[0])) return;
18493
- if (isIndexedArrayElementWithStringArgument(receiver, node.arguments?.[0])) return;
18914
+ if (isSubstringSearchLiteral(query)) return;
18915
+ if (isIndexedArrayElementWithStringArgument(receiver, query)) return;
18494
18916
  const resolvedInitializer = getResolvedInitializer(receiver);
18495
18917
  if (resolvedInitializer) {
18496
18918
  if (isLikelyStringReceiver(resolvedInitializer.initializer)) return;
@@ -25123,8 +25545,11 @@ const unwrapUseCallback = (node) => {
25123
25545
  const callee = node.callee;
25124
25546
  return isNodeOfType(callee, "Identifier") && callee.name === "useCallback" || isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useCallback" ? node.arguments?.[0] : node;
25125
25547
  };
25548
+ const hasParameterDefinition = (ref) => Boolean(ref.resolved?.defs.some((definition) => definition.type === "Parameter"));
25126
25549
  const resolveToFunction = (ref) => {
25127
- const definitionNode = ref.resolved?.defs[0]?.node;
25550
+ const definition = ref.resolved?.defs[0];
25551
+ if (!definition || hasParameterDefinition(ref)) return null;
25552
+ const definitionNode = definition.node;
25128
25553
  if (!definitionNode) return null;
25129
25554
  if (isFunctionLike$2(definitionNode)) return definitionNode;
25130
25555
  if (isNodeOfType(definitionNode, "VariableDeclarator")) {
@@ -25534,7 +25959,7 @@ const isCleanupReturnArgument = (analysis, node) => {
25534
25959
  if (isNodeOfType(node, "MemberExpression")) return true;
25535
25960
  if (isNodeOfType(node, "Identifier")) {
25536
25961
  const ref = getRef(analysis, node);
25537
- if (ref && resolveToFunction(ref)) return true;
25962
+ if (ref && (hasParameterDefinition(ref) || resolveToFunction(ref))) return true;
25538
25963
  }
25539
25964
  if (isNodeOfType(node, "ConditionalExpression")) return isCleanupReturnArgument(analysis, node.consequent) || isCleanupReturnArgument(analysis, node.alternate);
25540
25965
  return false;
@@ -26006,7 +26431,7 @@ const resolveWrappedCallable = (analysis, node) => {
26006
26431
  if (isNodeOfType(candidate, "Identifier")) {
26007
26432
  const reference = getRef(analysis, candidate);
26008
26433
  if (!reference) return null;
26009
- if (reference.resolved?.defs.some((definition) => definition.type === "Parameter" || definition.type === "ImportBinding")) return null;
26434
+ if (reference.resolved?.defs.some((definition) => definition.type === "ImportBinding")) return null;
26010
26435
  const resolved = resolveToFunction(reference);
26011
26436
  if (resolved) return resolved;
26012
26437
  const definitionNode = reference.resolved?.defs[0]?.node;
@@ -37815,8 +38240,6 @@ const getWrapperHookWrappedFunction = (initializer) => {
37815
38240
  if (!wrapped || !isFunctionLike$2(wrapped)) return null;
37816
38241
  return wrapped;
37817
38242
  };
37818
- const hasParameterDef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "Parameter"));
37819
- const resolvesToFunctionBinding = (ref) => !hasParameterDef(ref) && Boolean(resolveToFunction(ref));
37820
38243
  const HANDLER_NAMED_PROP_PATTERN = /^(on|handle)[A-Z]/;
37821
38244
  const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstreamRefs(analysis, wrappedFunction).some((innerRef) => {
37822
38245
  if (!isProp(analysis, innerRef)) return false;
@@ -38137,7 +38560,7 @@ const noPassDataToParent = defineRule({
38137
38560
  if (isConstant(argRef)) return false;
38138
38561
  if (isParentWiredHookResultRef(analysis, argRef)) return false;
38139
38562
  if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
38140
- if (resolvesToFunctionBinding(argRef)) return false;
38563
+ if (resolveToFunction(argRef)) return false;
38141
38564
  const argIdentifier = argRef.identifier;
38142
38565
  if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
38143
38566
  if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.7-dev.bdd1321",
3
+ "version": "0.7.7-dev.c79c897",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",