oxlint-plugin-react-doctor 0.7.5-dev.18e8717 → 0.7.5-dev.20cd922

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 +61 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -23998,6 +23998,26 @@ const STATEFUL_HTML_DESCENDANT_TAGS = new Set([
23998
23998
  "dialog",
23999
23999
  "canvas"
24000
24000
  ]);
24001
+ const STATEFUL_HTML_ATTRIBUTE_NAMES = new Set([
24002
+ "autofocus",
24003
+ "contenteditable",
24004
+ "draggable",
24005
+ "tabindex"
24006
+ ]);
24007
+ const isStaticallyFalseAttributeValue = (attribute) => {
24008
+ if (!isNodeOfType(attribute, "JSXAttribute") || !attribute.value) return false;
24009
+ const value = isNodeOfType(attribute.value, "JSXExpressionContainer") ? attribute.value.expression : attribute.value;
24010
+ return isNodeOfType(value, "Literal") && (value.value === false || value.value === "false");
24011
+ };
24012
+ const hasStatefulHtmlAttribute = (openingElement) => {
24013
+ if (!isNodeOfType(openingElement, "JSXOpeningElement")) return false;
24014
+ return openingElement.attributes.some((attribute) => {
24015
+ if (!isNodeOfType(attribute, "JSXAttribute") || !isNodeOfType(attribute.name, "JSXIdentifier")) return false;
24016
+ const attributeName = attribute.name.name.toLowerCase();
24017
+ if (!STATEFUL_HTML_ATTRIBUTE_NAMES.has(attributeName)) return false;
24018
+ return attributeName === "tabindex" || !isStaticallyFalseAttributeValue(attribute);
24019
+ });
24020
+ };
24001
24021
  const STATEFUL_DESCENDANT_SCAN_BUDGET = 200;
24002
24022
  const rootIdentifierNameOf = (expression) => {
24003
24023
  let object = expression;
@@ -24015,7 +24035,8 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
24015
24035
  budget -= 1;
24016
24036
  const node = stack.pop();
24017
24037
  if (isNodeOfType(node, "JSXElement")) {
24018
- const name = node.openingElement.name;
24038
+ const opening = node.openingElement;
24039
+ const name = opening.name;
24019
24040
  if (name && isNodeOfType(name, "JSXIdentifier")) {
24020
24041
  const tagName = name.name;
24021
24042
  const firstChar = tagName.charCodeAt(0);
@@ -24023,6 +24044,7 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
24023
24044
  if (STATEFUL_HTML_DESCENDANT_TAGS.has(tagName)) return true;
24024
24045
  }
24025
24046
  if (name && isNodeOfType(name, "JSXMemberExpression")) return true;
24047
+ if (hasStatefulHtmlAttribute(opening)) return true;
24026
24048
  const children = node.children ?? [];
24027
24049
  for (const child of children) stack.push(child);
24028
24050
  continue;
@@ -24582,6 +24604,14 @@ const templateHasOuterMemberIdentity = (template, bindingFunction) => {
24582
24604
  }
24583
24605
  return false;
24584
24606
  };
24607
+ const findBareItemNamesReferencedByTemplate = (template, itemNames) => {
24608
+ const referencedItemNames = /* @__PURE__ */ new Set();
24609
+ for (const expression of template.expressions ?? []) {
24610
+ const unwrappedExpression = stripParenExpression(expression);
24611
+ if (isNodeOfType(unwrappedExpression, "Identifier") && itemNames.has(unwrappedExpression.name)) referencedItemNames.add(unwrappedExpression.name);
24612
+ }
24613
+ return referencedItemNames;
24614
+ };
24585
24615
  const forLoopTestReadsDataLength = (test) => {
24586
24616
  let didFindLengthRead = false;
24587
24617
  walkAst(test, (child) => {
@@ -24718,9 +24748,11 @@ const noArrayIndexAsKey = defineRule({
24718
24748
  } else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
24719
24749
  const jsxElement = openingElement.parent;
24720
24750
  if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
24751
+ const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
24752
+ const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET;
24721
24753
  if (!containsStatefulDescendant(jsxElement, {
24722
- memberRootNames: INLINE_TEXT_LEAF_TAGS.has(elementName.name) ? itemNames : EMPTY_NAME_SET,
24723
- bareIdentifierNames: derivedNames
24754
+ memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET,
24755
+ bareIdentifierNames: primitiveItemNames.size > 0 ? new Set([...derivedNames, ...primitiveItemNames]) : derivedNames
24724
24756
  })) return;
24725
24757
  }
24726
24758
  }
@@ -35247,6 +35279,27 @@ const hasPreviousValueDep = (effectNode, depElements) => {
35247
35279
  }
35248
35280
  return false;
35249
35281
  };
35282
+ const isStateLikeDependency = (analysis, element, isPropName) => {
35283
+ if (!isNodeOfType(element, "Identifier") || isPropName(element.name)) return false;
35284
+ if (!analysis) return true;
35285
+ const reference = getRef(analysis, element);
35286
+ if (!reference) return true;
35287
+ const upstreamReferences = getUpstreamRefs(analysis, reference);
35288
+ if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
35289
+ return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
35290
+ };
35291
+ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
35292
+ const callee = stripParenExpression(callExpression.callee);
35293
+ if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "current") return null;
35294
+ const receiver = stripParenExpression(callee.object);
35295
+ if (!isNodeOfType(receiver, "Identifier")) return null;
35296
+ const binding = findVariableInitializer(callExpression, receiver.name);
35297
+ if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
35298
+ if (getCalleeName$2(binding.initializer) !== "useRef") return null;
35299
+ const callbackArgument = binding.initializer.arguments?.[0];
35300
+ if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
35301
+ return isPropName(callbackArgument.name) ? callbackArgument.name : null;
35302
+ };
35250
35303
  const noPropCallbackInEffect = defineRule({
35251
35304
  id: "no-prop-callback-in-effect",
35252
35305
  title: "Parent kept in sync with a callback effect",
@@ -35263,11 +35316,11 @@ const noPropCallbackInEffect = defineRule({
35263
35316
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
35264
35317
  const depsNode = node.arguments[1];
35265
35318
  if (!isNodeOfType(depsNode, "ArrayExpression") || !depsNode.elements?.length) return;
35266
- const stateLikeDeps = (depsNode.elements ?? []).filter((element) => isNodeOfType(element, "Identifier") && !propStackTracker.isPropName(element.name));
35319
+ const analysis = getProgramAnalysis(node);
35320
+ const stateLikeDeps = (depsNode.elements ?? []).filter((element) => element && isStateLikeDependency(analysis, element, propStackTracker.isPropName));
35267
35321
  if (stateLikeDeps.length === 0) return;
35268
35322
  if (isRefLatchGuardedEffect(callback.body)) return;
35269
35323
  if (hasPreviousValueDep(node, depsNode.elements ?? [])) return;
35270
- const analysis = getProgramAnalysis(node);
35271
35324
  if (analysis) {
35272
35325
  const stateLikeDepRefs = [];
35273
35326
  for (const element of stateLikeDeps) {
@@ -35279,9 +35332,9 @@ const noPropCallbackInEffect = defineRule({
35279
35332
  const reportedNodes = /* @__PURE__ */ new Set();
35280
35333
  walkInsideStatementBlocks(callback.body, (child) => {
35281
35334
  if (!isNodeOfType(child, "CallExpression")) return;
35282
- if (!isNodeOfType(child.callee, "Identifier")) return;
35283
- const calleeName = child.callee.name;
35284
- if (!propStackTracker.isPropName(calleeName)) return;
35335
+ const directCallee = stripParenExpression(child.callee);
35336
+ const calleeName = isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName);
35337
+ if (!calleeName) return;
35285
35338
  if (!isResultDiscardedCall(child)) return;
35286
35339
  if (reportedNodes.has(child)) return;
35287
35340
  reportedNodes.add(child);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.5-dev.18e8717",
3
+ "version": "0.7.5-dev.20cd922",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",