oxlint-plugin-react-doctor 0.7.7-dev.dc5c828 → 0.7.7-dev.e632f8a

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 +429 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4573,6 +4573,23 @@ const hasPossibleStaticPropertyWrite = (identifier, propertyName, scopes) => {
4573
4573
  return writtenPropertyName === null || writtenPropertyName === propertyName;
4574
4574
  }));
4575
4575
  };
4576
+ const canExecuteBefore = (candidateNode, referenceNode, scopes) => {
4577
+ const candidateBoundary = findExecutionBoundary(candidateNode);
4578
+ const referenceBoundary = findExecutionBoundary(referenceNode);
4579
+ if (!candidateBoundary || !referenceBoundary) return true;
4580
+ if (candidateBoundary === referenceBoundary) return candidateNode.range[0] < referenceNode.range[0];
4581
+ if (!isFunctionLike$1(candidateBoundary)) return true;
4582
+ return isFunctionSynchronouslyInvokedBefore(candidateBoundary, referenceNode, scopes);
4583
+ };
4584
+ const hasPossibleStaticPropertyWriteBefore = (identifier, propertyName, referenceNode, scopes) => {
4585
+ if (!isNodeOfType(identifier, "Identifier")) return false;
4586
+ return getPotentiallyAliasedSymbols(identifier, scopes).some((symbol) => symbol.references.some((reference) => {
4587
+ const writeTarget = getMemberWriteTarget(reference.identifier);
4588
+ if (!writeTarget || !canExecuteBefore(writeTarget, referenceNode, scopes)) return false;
4589
+ const writtenPropertyName = getResolvedStaticPropertyName(writeTarget, scopes);
4590
+ return writtenPropertyName === null || writtenPropertyName === propertyName;
4591
+ }));
4592
+ };
4576
4593
  const hasPossibleStaticPropertyMutationOrEscape = (identifier, propertyName, scopes) => {
4577
4594
  if (!isNodeOfType(identifier, "Identifier")) return false;
4578
4595
  if (hasPossibleStaticPropertyWrite(identifier, propertyName, scopes)) return true;
@@ -20147,14 +20164,14 @@ const isIndexOfResultUsedAsMembershipTest = (node) => {
20147
20164
  if (isNegativeOneLiteral(otherOperand)) return true;
20148
20165
  return isZeroLiteral(otherOperand) && (parent.operator === ">=" || parent.operator === "<");
20149
20166
  };
20150
- const getTypeAnnotation = (node) => {
20167
+ const getTypeAnnotation$1 = (node) => {
20151
20168
  if (!node || !("typeAnnotation" in node)) return null;
20152
20169
  const annotation = node.typeAnnotation;
20153
20170
  if (!annotation || !isNodeOfType(annotation, "TSTypeAnnotation")) return null;
20154
20171
  return annotation.typeAnnotation;
20155
20172
  };
20156
20173
  const getDeclaredPropertyType = (members, propertyName) => {
20157
- for (const member of members) if (isNodeOfType(member, "TSPropertySignature") && isNodeOfType(member.key, "Identifier") && member.key.name === propertyName) return getTypeAnnotation(member);
20174
+ for (const member of members) if (isNodeOfType(member, "TSPropertySignature") && isNodeOfType(member.key, "Identifier") && member.key.name === propertyName) return getTypeAnnotation$1(member);
20158
20175
  return null;
20159
20176
  };
20160
20177
  const getArrayElementType = (typeNode) => {
@@ -20173,7 +20190,7 @@ const getDestructuredDeclaredType = (identifier) => {
20173
20190
  const property = binding.bindingIdentifier.parent;
20174
20191
  const objectPattern = property?.parent;
20175
20192
  if (!isNodeOfType(property, "Property") || !isNodeOfType(property.key, "Identifier") || !isNodeOfType(objectPattern, "ObjectPattern")) return null;
20176
- const propsType = getTypeAnnotation(objectPattern);
20193
+ const propsType = getTypeAnnotation$1(objectPattern);
20177
20194
  if (!propsType) return null;
20178
20195
  if (isNodeOfType(propsType, "TSTypeLiteral")) return getDeclaredPropertyType(propsType.members ?? [], property.key.name);
20179
20196
  if (!isNodeOfType(propsType, "TSTypeReference") || !isNodeOfType(propsType.typeName, "Identifier")) return null;
@@ -20190,7 +20207,7 @@ const getIdentifierDeclaredType = (identifier, visitedBindingIdentifiers = /* @_
20190
20207
  const binding = findVariableInitializer(identifier, identifier.name);
20191
20208
  if (!binding || visitedBindingIdentifiers.has(binding.bindingIdentifier)) return null;
20192
20209
  visitedBindingIdentifiers.add(binding.bindingIdentifier);
20193
- const directType = getTypeAnnotation(binding.bindingIdentifier);
20210
+ const directType = getTypeAnnotation$1(binding.bindingIdentifier);
20194
20211
  if (directType) return directType;
20195
20212
  const initializer = binding.initializer;
20196
20213
  if (isNodeOfType(initializer, "TSAsExpression") || isNodeOfType(initializer, "TSTypeAssertion") || isNodeOfType(initializer, "TSSatisfiesExpression")) return initializer.typeAnnotation;
@@ -27355,6 +27372,211 @@ const computeExternallyDriven = (analysis, declarator) => {
27355
27372
  return hasDeferredCallSite;
27356
27373
  };
27357
27374
  //#endregion
27375
+ //#region src/plugin/rules/state-and-effects/utils/effect/is-proven-native-read-method.ts
27376
+ const ARRAY_READ_METHOD_NAMES$1 = new Set([
27377
+ "at",
27378
+ "concat",
27379
+ "entries",
27380
+ "every",
27381
+ "filter",
27382
+ "find",
27383
+ "findIndex",
27384
+ "findLast",
27385
+ "findLastIndex",
27386
+ "flat",
27387
+ "flatMap",
27388
+ "forEach",
27389
+ "includes",
27390
+ "indexOf",
27391
+ "join",
27392
+ "keys",
27393
+ "lastIndexOf",
27394
+ "map",
27395
+ "reduce",
27396
+ "reduceRight",
27397
+ "slice",
27398
+ "some",
27399
+ "toLocaleString",
27400
+ "toReversed",
27401
+ "toSorted",
27402
+ "toSpliced",
27403
+ "toString",
27404
+ "values",
27405
+ "with"
27406
+ ]);
27407
+ const MAP_READ_METHOD_NAMES = new Set([
27408
+ "entries",
27409
+ "forEach",
27410
+ "get",
27411
+ "has",
27412
+ "keys",
27413
+ "values"
27414
+ ]);
27415
+ const SET_READ_METHOD_NAMES = new Set([
27416
+ "difference",
27417
+ "entries",
27418
+ "forEach",
27419
+ "has",
27420
+ "intersection",
27421
+ "isDisjointFrom",
27422
+ "isSubsetOf",
27423
+ "isSupersetOf",
27424
+ "keys",
27425
+ "symmetricDifference",
27426
+ "union",
27427
+ "values"
27428
+ ]);
27429
+ const FUNCTION_READ_METHOD_NAMES = new Set(["bind"]);
27430
+ const PROMISE_READ_METHOD_NAMES = new Set([
27431
+ "catch",
27432
+ "finally",
27433
+ "then"
27434
+ ]);
27435
+ const STRING_READ_METHOD_NAMES$1 = new Set([
27436
+ "at",
27437
+ "charAt",
27438
+ "charCodeAt",
27439
+ "codePointAt",
27440
+ "concat",
27441
+ "endsWith",
27442
+ "includes",
27443
+ "indexOf",
27444
+ "isWellFormed",
27445
+ "lastIndexOf",
27446
+ "localeCompare",
27447
+ "match",
27448
+ "matchAll",
27449
+ "normalize",
27450
+ "padEnd",
27451
+ "padStart",
27452
+ "repeat",
27453
+ "replace",
27454
+ "replaceAll",
27455
+ "search",
27456
+ "slice",
27457
+ "split",
27458
+ "startsWith",
27459
+ "substring",
27460
+ "toLocaleLowerCase",
27461
+ "toLocaleUpperCase",
27462
+ "toLowerCase",
27463
+ "toString",
27464
+ "toUpperCase",
27465
+ "toWellFormed",
27466
+ "trim",
27467
+ "trimEnd",
27468
+ "trimStart",
27469
+ "valueOf"
27470
+ ]);
27471
+ const NATIVE_TYPE_NAMES = new Set([
27472
+ "Array",
27473
+ "Map",
27474
+ "Promise",
27475
+ "PromiseLike",
27476
+ "ReadonlyArray",
27477
+ "ReadonlyMap",
27478
+ "ReadonlySet",
27479
+ "Set"
27480
+ ]);
27481
+ const nativeTypeDeclarationsByProgram = /* @__PURE__ */ new WeakMap();
27482
+ const shadowedNativeTypeNamesByBinding = /* @__PURE__ */ new WeakMap();
27483
+ const isWithinNode = (node, ancestor) => {
27484
+ let current = node;
27485
+ while (current) {
27486
+ if (current === ancestor) return true;
27487
+ current = current.parent;
27488
+ }
27489
+ return false;
27490
+ };
27491
+ const findTypeDeclarationScope = (declaration) => {
27492
+ let current = declaration.parent;
27493
+ while (current) {
27494
+ if (isNodeOfType(current, "Program") || isNodeOfType(current, "BlockStatement") || isNodeOfType(current, "TSModuleBlock") || isNodeOfType(current, "StaticBlock")) return current;
27495
+ current = current.parent;
27496
+ }
27497
+ return null;
27498
+ };
27499
+ const getDeclaredTypeName = (declaration) => {
27500
+ if (!isNodeOfType(declaration, "ClassDeclaration") && !isNodeOfType(declaration, "TSEnumDeclaration") && !isNodeOfType(declaration, "TSImportEqualsDeclaration") && !isNodeOfType(declaration, "TSInterfaceDeclaration") && !isNodeOfType(declaration, "TSModuleDeclaration") && !isNodeOfType(declaration, "TSTypeAliasDeclaration")) return null;
27501
+ return declaration.id && isNodeOfType(declaration.id, "Identifier") ? declaration.id.name : null;
27502
+ };
27503
+ const getNativeTypeDeclarations = (program) => {
27504
+ const cachedDeclarations = nativeTypeDeclarationsByProgram.get(program);
27505
+ if (cachedDeclarations) return cachedDeclarations;
27506
+ const declarations = [];
27507
+ walkAst(program, (candidate) => {
27508
+ if (isNodeOfType(candidate, "ImportDeclaration")) {
27509
+ for (const specifier of candidate.specifiers) if (NATIVE_TYPE_NAMES.has(specifier.local.name)) declarations.push({
27510
+ name: specifier.local.name,
27511
+ scope: program
27512
+ });
27513
+ return;
27514
+ }
27515
+ const declaredTypeName = getDeclaredTypeName(candidate);
27516
+ if (!declaredTypeName || !NATIVE_TYPE_NAMES.has(declaredTypeName)) return;
27517
+ const declarationScope = findTypeDeclarationScope(candidate);
27518
+ if (declarationScope) declarations.push({
27519
+ name: declaredTypeName,
27520
+ scope: declarationScope
27521
+ });
27522
+ });
27523
+ nativeTypeDeclarationsByProgram.set(program, declarations);
27524
+ return declarations;
27525
+ };
27526
+ const getShadowedNativeTypeNames = (binding) => {
27527
+ const cachedNames = shadowedNativeTypeNamesByBinding.get(binding);
27528
+ if (cachedNames) return cachedNames;
27529
+ const shadowedNames = /* @__PURE__ */ new Set();
27530
+ for (const typeName of NATIVE_TYPE_NAMES) if (hasEnclosingTypeParameterNamed(binding, typeName)) shadowedNames.add(typeName);
27531
+ const program = findProgramRoot(binding);
27532
+ if (program) {
27533
+ for (const declaration of getNativeTypeDeclarations(program)) if (isWithinNode(binding, declaration.scope)) shadowedNames.add(declaration.name);
27534
+ }
27535
+ shadowedNativeTypeNamesByBinding.set(binding, shadowedNames);
27536
+ return shadowedNames;
27537
+ };
27538
+ const getParameterBinding = (definitionName) => {
27539
+ if (isNodeOfType(definitionName, "Identifier")) return definitionName;
27540
+ if (!isNodeOfType(definitionName, "AssignmentPattern")) return null;
27541
+ return isNodeOfType(definitionName.left, "Identifier") ? definitionName.left : null;
27542
+ };
27543
+ const getTypeAnnotation = (binding) => {
27544
+ if (!isNodeOfType(binding, "Identifier")) return null;
27545
+ const annotation = binding.typeAnnotation;
27546
+ return annotation && isNodeOfType(annotation, "TSTypeAnnotation") ? annotation.typeAnnotation : null;
27547
+ };
27548
+ const isNullishType = (typeNode) => isNodeOfType(typeNode, "TSNullKeyword") || isNodeOfType(typeNode, "TSUndefinedKeyword");
27549
+ const isNativeReadMethod = (typeNode, methodName, shadowedTypeNames) => {
27550
+ if (isNodeOfType(typeNode, "TSArrayType") || isNodeOfType(typeNode, "TSTupleType")) return ARRAY_READ_METHOD_NAMES$1.has(methodName);
27551
+ if (isNodeOfType(typeNode, "TSStringKeyword")) return STRING_READ_METHOD_NAMES$1.has(methodName);
27552
+ if (isNodeOfType(typeNode, "TSFunctionType")) return FUNCTION_READ_METHOD_NAMES.has(methodName);
27553
+ if (isNodeOfType(typeNode, "TSLiteralType") && isNodeOfType(typeNode.literal, "Literal") && typeof typeNode.literal.value === "string") return STRING_READ_METHOD_NAMES$1.has(methodName);
27554
+ if (isNodeOfType(typeNode, "TSTypeOperator") && typeNode.operator === "readonly" && typeNode.typeAnnotation) return isNativeReadMethod(typeNode.typeAnnotation, methodName, shadowedTypeNames);
27555
+ if (isNodeOfType(typeNode, "TSUnionType")) {
27556
+ let hasNonNullishMember = false;
27557
+ for (const memberType of typeNode.types) {
27558
+ if (isNullishType(memberType)) continue;
27559
+ hasNonNullishMember = true;
27560
+ if (!isNativeReadMethod(memberType, methodName, shadowedTypeNames)) return false;
27561
+ }
27562
+ return hasNonNullishMember;
27563
+ }
27564
+ if (!isNodeOfType(typeNode, "TSTypeReference") || !isNodeOfType(typeNode.typeName, "Identifier")) return false;
27565
+ if (shadowedTypeNames.has(typeNode.typeName.name)) return false;
27566
+ if (typeNode.typeName.name === "Array" || typeNode.typeName.name === "ReadonlyArray") return ARRAY_READ_METHOD_NAMES$1.has(methodName);
27567
+ if (typeNode.typeName.name === "Map" || typeNode.typeName.name === "ReadonlyMap") return MAP_READ_METHOD_NAMES.has(methodName);
27568
+ if (typeNode.typeName.name === "Set" || typeNode.typeName.name === "ReadonlySet") return SET_READ_METHOD_NAMES.has(methodName);
27569
+ if (typeNode.typeName.name === "Promise" || typeNode.typeName.name === "PromiseLike") return PROMISE_READ_METHOD_NAMES.has(methodName);
27570
+ return false;
27571
+ };
27572
+ const isProvenNativeReadMethod = (ref, methodName) => Boolean(ref.resolved?.defs.some((definition) => {
27573
+ if (definition.type !== "Parameter") return false;
27574
+ const parameterBinding = getParameterBinding(definition.name);
27575
+ if (!parameterBinding) return false;
27576
+ const typeAnnotation = getTypeAnnotation(parameterBinding);
27577
+ return Boolean(typeAnnotation && isNativeReadMethod(typeAnnotation, methodName, getShadowedNativeTypeNames(parameterBinding)));
27578
+ }));
27579
+ //#endregion
27358
27580
  //#region src/plugin/rules/state-and-effects/utils/effect/react.ts
27359
27581
  const KNOWN_PURE_HOC_NAMES = new Set(["memo", "forwardRef"]);
27360
27582
  const startsWithUppercase = (name) => Boolean(name && name.length > 0 && name[0] >= "A" && name[0] <= "Z");
@@ -27581,7 +27803,132 @@ const isRefCurrent = (ref) => {
27581
27803
  const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isStateSetter(analysis, innerRef));
27582
27804
  const isSyncStateSetterCall = (analysis, ref, effectFn) => isStateSetterCall(analysis, ref) && isSynchronous(ref.identifier, effectFn) && !resolvesToAsyncFunction(ref);
27583
27805
  const HANDLER_NAMED_METHOD_PATTERN = /^(on|handle)[A-Z]/;
27584
- const isPropCallbackInvocationRef = (analysis, ref) => {
27806
+ const SYNCHRONOUS_CALLBACK_ARGUMENT_INDEX_BY_METHOD = new Map([
27807
+ ["every", 0],
27808
+ ["filter", 0],
27809
+ ["find", 0],
27810
+ ["findIndex", 0],
27811
+ ["findLast", 0],
27812
+ ["findLastIndex", 0],
27813
+ ["flatMap", 0],
27814
+ ["forEach", 0],
27815
+ ["map", 0],
27816
+ ["reduce", 0],
27817
+ ["reduceRight", 0],
27818
+ ["replace", 1],
27819
+ ["replaceAll", 1],
27820
+ ["some", 0]
27821
+ ]);
27822
+ const addSimpleBindingNames = (pattern, names) => {
27823
+ if (isNodeOfType(pattern, "Identifier")) {
27824
+ if (names.has(pattern.name)) return false;
27825
+ names.add(pattern.name);
27826
+ return true;
27827
+ }
27828
+ if (isNodeOfType(pattern, "AssignmentPattern")) return addSimpleBindingNames(pattern.left, names);
27829
+ if (isNodeOfType(pattern, "RestElement")) return addSimpleBindingNames(pattern.argument, names);
27830
+ if (isNodeOfType(pattern, "ObjectPattern")) {
27831
+ let didAddName = false;
27832
+ for (const property of pattern.properties) didAddName = addSimpleBindingNames(isNodeOfType(property, "Property") ? property.value : property, names) || didAddName;
27833
+ return didAddName;
27834
+ }
27835
+ if (isNodeOfType(pattern, "ArrayPattern")) {
27836
+ let didAddName = false;
27837
+ for (const element of pattern.elements) if (element) didAddName = addSimpleBindingNames(element, names) || didAddName;
27838
+ return didAddName;
27839
+ }
27840
+ return false;
27841
+ };
27842
+ const getSimpleParameterNames = (callback) => {
27843
+ if (!isFunctionLike$1(callback)) return /* @__PURE__ */ new Set();
27844
+ const names = /* @__PURE__ */ new Set();
27845
+ for (const parameter of callback.params ?? []) addSimpleBindingNames(parameter, names);
27846
+ return names;
27847
+ };
27848
+ const inlineCallbackInvokesParameter = (callback) => {
27849
+ const parameterNames = getSimpleParameterNames(callback);
27850
+ if (parameterNames.size === 0 || !isFunctionLike$1(callback)) return false;
27851
+ let didAddAlias = true;
27852
+ while (didAddAlias) {
27853
+ didAddAlias = false;
27854
+ walkAst(callback.body, (candidate) => {
27855
+ if (candidate !== callback.body && isFunctionLike$1(candidate)) return false;
27856
+ if (isNodeOfType(candidate, "AssignmentExpression") && candidate.operator === "=" && isNodeOfType(candidate.left, "Identifier")) {
27857
+ const assignedValueRoot = getRootIdentifier$1(candidate.right);
27858
+ if (isNodeOfType(assignedValueRoot, "Identifier") && parameterNames.has(assignedValueRoot.name) && !parameterNames.has(candidate.left.name)) {
27859
+ parameterNames.add(candidate.left.name);
27860
+ didAddAlias = true;
27861
+ }
27862
+ return;
27863
+ }
27864
+ if (!isNodeOfType(candidate, "VariableDeclarator") || !candidate.init) return;
27865
+ const initializerRoot = getRootIdentifier$1(candidate.init);
27866
+ if (!isNodeOfType(initializerRoot, "Identifier") || !parameterNames.has(initializerRoot.name)) return;
27867
+ if (addSimpleBindingNames(candidate.id, parameterNames)) didAddAlias = true;
27868
+ });
27869
+ }
27870
+ let didInvokeParameter = false;
27871
+ walkAst(callback.body, (candidate) => {
27872
+ if (didInvokeParameter) return false;
27873
+ if (candidate !== callback.body && isFunctionLike$1(candidate)) return false;
27874
+ if (!isNodeOfType(candidate, "CallExpression")) return;
27875
+ const callee = stripParenExpression(candidate.callee);
27876
+ if (isNodeOfType(callee, "Identifier") && parameterNames.has(callee.name)) {
27877
+ didInvokeParameter = true;
27878
+ return false;
27879
+ }
27880
+ if (isNodeOfType(callee, "MemberExpression")) {
27881
+ const receiverRoot = getRootIdentifier$1(callee.object);
27882
+ if (receiverRoot && parameterNames.has(receiverRoot.name)) {
27883
+ didInvokeParameter = true;
27884
+ return false;
27885
+ }
27886
+ }
27887
+ });
27888
+ return didInvokeParameter;
27889
+ };
27890
+ const callbackInvokesPropCallback = (analysis, callback) => {
27891
+ if (!isFunctionLike$1(callback)) return false;
27892
+ let didInvokePropCallback = false;
27893
+ walkAst(callback.body, (candidate) => {
27894
+ if (didInvokePropCallback) return false;
27895
+ if (candidate !== callback.body && isFunctionLike$1(candidate)) return false;
27896
+ if (!isNodeOfType(candidate, "CallExpression")) return;
27897
+ if (getDownstreamRefs(analysis, stripParenExpression(candidate.callee)).some((reference) => isPropCallbackInvocationRef(analysis, reference))) {
27898
+ didInvokePropCallback = true;
27899
+ return false;
27900
+ }
27901
+ });
27902
+ return didInvokePropCallback;
27903
+ };
27904
+ const isNativeMethodSuppressionSafe = (analysis, callExpression, methodName, scopes) => {
27905
+ if (!isNodeOfType(callExpression, "CallExpression")) return false;
27906
+ const callee = stripParenExpression(callExpression.callee);
27907
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
27908
+ const receiver = stripParenExpression(callee.object);
27909
+ if (!isNodeOfType(receiver, "Identifier")) return false;
27910
+ const receiverSymbol = scopes.symbolFor(receiver);
27911
+ if (!receiverSymbol || hasSymbolWriteBefore(receiverSymbol, callExpression, scopes) || hasPossibleStaticPropertyWriteBefore(receiver, methodName, callExpression, scopes)) return false;
27912
+ let callResult = callExpression;
27913
+ let callResultConsumer = callResult.parent;
27914
+ while (callResultConsumer && (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(callResultConsumer.type) && "expression" in callResultConsumer && callResultConsumer.expression === callResult || isNodeOfType(callResultConsumer, "MemberExpression") && callResultConsumer.object === callResult)) {
27915
+ callResult = callResultConsumer;
27916
+ callResultConsumer = callResult.parent;
27917
+ }
27918
+ if (isNodeOfType(callResultConsumer, "CallExpression") && callResultConsumer.callee === callResult) return false;
27919
+ const callbackArgumentIndex = SYNCHRONOUS_CALLBACK_ARGUMENT_INDEX_BY_METHOD.get(methodName);
27920
+ if (callbackArgumentIndex === void 0) return true;
27921
+ const callbackArgument = callExpression.arguments?.[callbackArgumentIndex];
27922
+ if (!callbackArgument) return true;
27923
+ const callbackValue = stripParenExpression(callbackArgument);
27924
+ if (isFunctionLike$1(callbackValue)) return !inlineCallbackInvokesParameter(callbackValue) && !callbackInvokesPropCallback(analysis, callbackValue);
27925
+ if (!isNodeOfType(callbackValue, "Identifier")) return false;
27926
+ const callbackRef = getRef(analysis, callbackValue);
27927
+ if (!callbackRef || isPropAlias(analysis, callbackRef)) return false;
27928
+ const callbackFunction = resolveToFunction(callbackRef);
27929
+ return Boolean(callbackFunction && isFunctionLike$1(callbackFunction) && !inlineCallbackInvokesParameter(callbackFunction) && !callbackInvokesPropCallback(analysis, callbackFunction));
27930
+ };
27931
+ const isPropCallbackInvocationRef = (analysis, ref, options = {}) => {
27585
27932
  if (!isPropAlias(analysis, ref)) return false;
27586
27933
  let effectiveNode = ref.identifier;
27587
27934
  let parent = effectiveNode.parent;
@@ -27594,7 +27941,9 @@ const isPropCallbackInvocationRef = (analysis, ref) => {
27594
27941
  if (isNodeOfType(parent, "MemberExpression") && parent.object === effectiveNode) {
27595
27942
  const memberParent = parent.parent;
27596
27943
  if (isNodeOfType(memberParent, "CallExpression") && memberParent.callee === parent) {
27597
- if (!parent.computed && isNodeOfType(parent.property, "Identifier") && HANDLER_NAMED_METHOD_PATTERN.test(parent.property.name)) return true;
27944
+ const propertyName = getStaticPropertyName(parent);
27945
+ if (propertyName && HANDLER_NAMED_METHOD_PATTERN.test(propertyName)) return true;
27946
+ if (options.nativeMethodScopes && isCustomHookParameter(ref) && propertyName && isProvenNativeReadMethod(ref, propertyName) && isNativeMethodSuppressionSafe(analysis, memberParent, propertyName, options.nativeMethodScopes)) return false;
27598
27947
  return isWholePropsObjectReference(analysis, ref);
27599
27948
  }
27600
27949
  }
@@ -41065,7 +41414,7 @@ const noPropCallbackInRender = defineRule({
41065
41414
  if (!analysis) return;
41066
41415
  const callee = stripParenExpression(node.callee);
41067
41416
  if (isFunctionLike$1(callee)) return;
41068
- if (!getDownstreamRefs(analysis, callee).some((reference) => isPropCallbackInvocationRef(analysis, reference))) return;
41417
+ if (!getDownstreamRefs(analysis, callee).some((reference) => isPropCallbackInvocationRef(analysis, reference, { nativeMethodScopes: context.scopes }))) return;
41069
41418
  context.report({
41070
41419
  node,
41071
41420
  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."
@@ -59029,6 +59378,78 @@ const inferDestructureSourceKey = (bindingIdentifier) => {
59029
59378
  return null;
59030
59379
  };
59031
59380
  const isReactUseHook = (hookName) => hookName === "use";
59381
+ const isReactHookCapabilityValue = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
59382
+ const unwrappedExpression = stripParenExpression(expression);
59383
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
59384
+ const symbol = scopes.symbolFor(unwrappedExpression);
59385
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
59386
+ visitedSymbolIds.add(symbol.id);
59387
+ if (symbol.kind === "import") return isReactImport(symbol) && isReactHookName(getImportedName(symbol.declarationNode) ?? "");
59388
+ if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
59389
+ const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
59390
+ if (destructuredPropertyName) {
59391
+ const namespaceExpression = stripParenExpression(symbol.initializer);
59392
+ return isReactHookName(destructuredPropertyName) && isNodeOfType(namespaceExpression, "Identifier") && isReactNamespaceImport(namespaceExpression, scopes) && !hasPossibleStaticPropertyMutationOrEscape(namespaceExpression, destructuredPropertyName, scopes);
59393
+ }
59394
+ if (symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
59395
+ return isReactHookCapabilityValue(symbol.initializer, scopes, visitedSymbolIds);
59396
+ }
59397
+ if (!isNodeOfType(unwrappedExpression, "MemberExpression")) return false;
59398
+ const propertyName = getStaticPropertyName(unwrappedExpression);
59399
+ if (!propertyName || !isReactHookName(propertyName)) return false;
59400
+ const namespaceExpression = stripParenExpression(unwrappedExpression.object);
59401
+ return isNodeOfType(namespaceExpression, "Identifier") && isReactNamespaceImport(namespaceExpression, scopes) && !hasPossibleStaticPropertyMutationOrEscape(namespaceExpression, propertyName, scopes);
59402
+ };
59403
+ const isReactHookCapabilityComparisonOperand = (expression, scopes) => {
59404
+ const unwrappedExpression = stripParenExpression(expression);
59405
+ if (isReactHookCapabilityValue(unwrappedExpression, scopes)) return true;
59406
+ return isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "typeof" && isReactHookCapabilityValue(unwrappedExpression.argument, scopes);
59407
+ };
59408
+ const isStaticCapabilityComparisonValue = (expression, scopes) => {
59409
+ const unwrappedExpression = stripParenExpression(expression);
59410
+ if (isNodeOfType(unwrappedExpression, "Literal")) return true;
59411
+ return isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "undefined" && scopes.isGlobalReference(unwrappedExpression);
59412
+ };
59413
+ const CAPABILITY_COMPARISON_OPERATORS = new Set([
59414
+ "==",
59415
+ "!=",
59416
+ "===",
59417
+ "!=="
59418
+ ]);
59419
+ const isInvariantReactHookCapabilityCondition = (expression, scopes) => {
59420
+ const unwrappedExpression = stripParenExpression(expression);
59421
+ if (isReactHookCapabilityValue(unwrappedExpression, scopes)) return true;
59422
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return isInvariantReactHookCapabilityCondition(unwrappedExpression.argument, scopes);
59423
+ if (isNodeOfType(unwrappedExpression, "LogicalExpression")) return isInvariantReactHookCapabilityCondition(unwrappedExpression.left, scopes) && isInvariantReactHookCapabilityCondition(unwrappedExpression.right, scopes);
59424
+ if (!isNodeOfType(unwrappedExpression, "BinaryExpression") || !CAPABILITY_COMPARISON_OPERATORS.has(unwrappedExpression.operator)) return false;
59425
+ return isReactHookCapabilityComparisonOperand(unwrappedExpression.left, scopes) && isStaticCapabilityComparisonValue(unwrappedExpression.right, scopes) || isReactHookCapabilityComparisonOperand(unwrappedExpression.right, scopes) && isStaticCapabilityComparisonValue(unwrappedExpression.left, scopes);
59426
+ };
59427
+ const statementContainsOwnAbruptCompletion = (statement) => {
59428
+ let doesContainAbruptCompletion = false;
59429
+ walkAst(statement, (child) => {
59430
+ if (child !== statement && isFunctionLike$1(child)) return false;
59431
+ if (isNodeOfType(child, "ReturnStatement") || isNodeOfType(child, "ThrowStatement")) {
59432
+ doesContainAbruptCompletion = true;
59433
+ return false;
59434
+ }
59435
+ });
59436
+ return doesContainAbruptCompletion;
59437
+ };
59438
+ const isInvariantReactHookCapabilityExit = (statement, scopes) => isNodeOfType(statement, "IfStatement") && statement.alternate === null && statementAlwaysExits(statement.consequent) && isInvariantReactHookCapabilityCondition(statement.test, scopes);
59439
+ const isAfterOnlyInvariantReactHookCapabilityExits = (node, enclosingFunction, scopes) => {
59440
+ if (!isFunctionLike$1(enclosingFunction) || !isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
59441
+ let containingStatement = node;
59442
+ while (containingStatement.parent && containingStatement.parent !== enclosingFunction.body) {
59443
+ if (isFunctionLike$1(containingStatement.parent)) return false;
59444
+ if (isNodeOfType(containingStatement.parent, "IfStatement") || isNodeOfType(containingStatement.parent, "SwitchStatement") || isNodeOfType(containingStatement.parent, "SwitchCase") || isNodeOfType(containingStatement.parent, "ConditionalExpression") || isNodeOfType(containingStatement.parent, "LogicalExpression")) return false;
59445
+ containingStatement = containingStatement.parent;
59446
+ }
59447
+ if (containingStatement.parent !== enclosingFunction.body) return false;
59448
+ const statementIndex = enclosingFunction.body.body.findIndex((statement) => statement === containingStatement);
59449
+ if (statementIndex <= 0) return false;
59450
+ const bypassingStatements = enclosingFunction.body.body.slice(0, statementIndex).filter((statement) => statementContainsOwnAbruptCompletion(statement));
59451
+ return bypassingStatements.length > 0 && bypassingStatements.every((statement) => isInvariantReactHookCapabilityExit(statement, scopes));
59452
+ };
59032
59453
  const getCallExpressionCalleeName = (callExpression) => {
59033
59454
  const callee = callExpression.callee;
59034
59455
  if (isNodeOfType(callee, "Identifier")) return callee.name;
@@ -59324,7 +59745,7 @@ const rulesOfHooks = defineRule({
59324
59745
  });
59325
59746
  return;
59326
59747
  }
59327
- if (!context.cfg.isUnconditionalFromEntry(node)) context.report({
59748
+ if (!context.cfg.isUnconditionalFromEntry(node) && !isAfterOnlyInvariantReactHookCapabilityExits(node, enclosing.node, context.scopes)) context.report({
59328
59749
  node: node.callee,
59329
59750
  message: buildConditionalMessage(hookName)
59330
59751
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.7-dev.dc5c828",
3
+ "version": "0.7.7-dev.e632f8a",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",