oxlint-plugin-react-doctor 0.7.6-dev.eb09902 → 0.7.6-dev.fbd85e3

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 +106 -204
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1518,37 +1518,6 @@ const hasJsxPropIgnoreCase = (attributes, targetProp) => {
1518
1518
  }
1519
1519
  };
1520
1520
  //#endregion
1521
- //#region src/plugin/utils/is-uppercase-name.ts
1522
- const isUppercaseName = (name) => UPPERCASE_PATTERN.test(name);
1523
- //#endregion
1524
- //#region src/plugin/utils/resolve-jsx-element-type.ts
1525
- const resolveConstantStringBinding = (name) => {
1526
- const binding = findVariableInitializer(name, name.name);
1527
- if (!binding?.initializer) return null;
1528
- const declarator = binding.bindingIdentifier.parent;
1529
- if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return null;
1530
- if (declarator.id !== binding.bindingIdentifier) return null;
1531
- const declaration = declarator.parent;
1532
- if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) return null;
1533
- if (declaration.kind !== "const") return null;
1534
- const initializer = stripParenExpression(binding.initializer);
1535
- return isNodeOfType(initializer, "Literal") && typeof initializer.value === "string" ? initializer.value : null;
1536
- };
1537
- const flattenJsxName$2 = (name) => {
1538
- if (isNodeOfType(name, "JSXIdentifier")) return name.name;
1539
- if (isNodeOfType(name, "JSXMemberExpression")) return `${flattenJsxName$2(name.object)}.${name.property.name}`;
1540
- if (isNodeOfType(name, "JSXNamespacedName")) return `${name.namespace.name}:${name.name.name}`;
1541
- return "";
1542
- };
1543
- const resolveJsxElementType = (openingElement) => {
1544
- const name = openingElement.name;
1545
- if (isNodeOfType(name, "JSXIdentifier")) {
1546
- if (!isUppercaseName(name.name)) return name.name;
1547
- return resolveConstantStringBinding(name) ?? name.name;
1548
- }
1549
- return flattenJsxName$2(name);
1550
- };
1551
- //#endregion
1552
1521
  //#region src/plugin/utils/get-element-type.ts
1553
1522
  const EMPTY_JSX_A11Y_SETTINGS = Object.freeze({});
1554
1523
  const jsxA11ySettingsCache = /* @__PURE__ */ new WeakMap();
@@ -1561,8 +1530,14 @@ const readJsxA11ySettings = (settings) => {
1561
1530
  jsxA11ySettingsCache.set(settings, a11ySettings);
1562
1531
  return a11ySettings;
1563
1532
  };
1533
+ const flattenJsxName$2 = (name) => {
1534
+ if (isNodeOfType(name, "JSXIdentifier")) return name.name;
1535
+ if (isNodeOfType(name, "JSXMemberExpression")) return `${flattenJsxName$2(name.object)}.${name.property.name}`;
1536
+ if (isNodeOfType(name, "JSXNamespacedName")) return `${name.namespace.name}:${name.name.name}`;
1537
+ return "";
1538
+ };
1564
1539
  const computeElementType = (openingElement, a11ySettings) => {
1565
- const baseName = resolveJsxElementType(openingElement);
1540
+ const baseName = flattenJsxName$2(openingElement.name);
1566
1541
  if (a11ySettings.polymorphicPropName) {
1567
1542
  const polymorphicAttribute = hasJsxPropIgnoreCase(openingElement.attributes, a11ySettings.polymorphicPropName);
1568
1543
  if (polymorphicAttribute) {
@@ -2397,9 +2372,8 @@ const anchorIsValid = defineRule({
2397
2372
  const isTestlikeFile = isTestlikeFilename(context.filename);
2398
2373
  return { JSXOpeningElement(node) {
2399
2374
  if (isTestlikeFile) return;
2400
- const tag = getElementType(node, context.settings);
2401
- if (!fileHasJsxA11ySettings && tag !== "a") return;
2402
- if (tag !== "a") return;
2375
+ if (!fileHasJsxA11ySettings && (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a")) return;
2376
+ if (getElementType(node, context.settings) !== "a") return;
2403
2377
  let hrefAttribute;
2404
2378
  for (const attributeName of settings.hrefAttributeNames) {
2405
2379
  hrefAttribute = hasJsxPropIgnoreCase(node.attributes, attributeName);
@@ -5754,7 +5728,7 @@ const buttonHasType = defineRule({
5754
5728
  return {
5755
5729
  JSXOpeningElement(node) {
5756
5730
  if (isTestlikeFile) return;
5757
- if (resolveJsxElementType(node) !== "button") return;
5731
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "button") return;
5758
5732
  const typeAttr = hasJsxPropIgnoreCase(node.attributes, "type");
5759
5733
  if (!typeAttr) {
5760
5734
  if (hasJsxSpreadAttribute$1(node.attributes)) {
@@ -5944,7 +5918,7 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
5944
5918
  };
5945
5919
  return {
5946
5920
  JSXOpeningElement(node) {
5947
- if (resolveJsxElementType(node) !== "input") return;
5921
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "input") return;
5948
5922
  reportFromPresence(collectFromJsxAttributes(node.attributes));
5949
5923
  },
5950
5924
  CallExpression(node) {
@@ -6021,7 +5995,7 @@ const isPureEventBlockerHandler = (attribute) => {
6021
5995
  //#endregion
6022
5996
  //#region src/plugin/utils/resolve-const-identifier-alias.ts
6023
5997
  const resolveConstIdentifierAlias = (identifier, scopes) => {
6024
- if (!isNodeOfType(identifier, "Identifier") && !isNodeOfType(identifier, "JSXIdentifier")) return null;
5998
+ if (!isNodeOfType(identifier, "Identifier")) return null;
6025
5999
  const visitedSymbolIds = /* @__PURE__ */ new Set();
6026
6000
  let symbol = scopes.symbolFor(identifier);
6027
6001
  while (symbol?.kind === "const") {
@@ -7459,7 +7433,7 @@ const findJsxAttribute = (attributes, attributeName) => attributes?.find((attrib
7459
7433
  const getOpeningElementTagName = (openingElement) => {
7460
7434
  if (!openingElement) return null;
7461
7435
  if (!isNodeOfType(openingElement, "JSXOpeningElement")) return null;
7462
- if (isNodeOfType(openingElement.name, "JSXIdentifier")) return resolveJsxElementType(openingElement);
7436
+ if (isNodeOfType(openingElement.name, "JSXIdentifier")) return openingElement.name.name;
7463
7437
  if (isNodeOfType(openingElement.name, "JSXMemberExpression")) {
7464
7438
  let cursor = openingElement.name;
7465
7439
  while (isNodeOfType(cursor, "JSXMemberExpression")) cursor = cursor.property;
@@ -7711,7 +7685,7 @@ const dialogHasAccessibleName = defineRule({
7711
7685
  if (isTestlikeFilename(context.filename)) return {};
7712
7686
  return { JSXOpeningElement(node) {
7713
7687
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
7714
- const tagName = resolveJsxElementType(node);
7688
+ const tagName = node.name.name;
7715
7689
  if (tagName[0] !== tagName[0]?.toLowerCase()) return;
7716
7690
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
7717
7691
  const roleValue = roleAttribute ? getJsxPropStringValue(roleAttribute) : null;
@@ -11943,7 +11917,7 @@ const forbidDomProps = defineRule({
11943
11917
  return { JSXOpeningElement(node) {
11944
11918
  if (forbidMap.size === 0) return;
11945
11919
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
11946
- const elementName = resolveJsxElementType(node);
11920
+ const elementName = node.name.name;
11947
11921
  if (isReactComponentName(elementName)) return;
11948
11922
  for (const attribute of node.attributes) {
11949
11923
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
@@ -12021,7 +11995,7 @@ const forbidElements = defineRule({
12021
11995
  return {
12022
11996
  JSXOpeningElement(node) {
12023
11997
  if (forbidMap.size === 0) return;
12024
- const fullName = resolveJsxElementType(node);
11998
+ const fullName = flattenJsxName$1(node.name);
12025
11999
  if (!fullName || !forbidMap.has(fullName)) return;
12026
12000
  context.report({
12027
12001
  node: node.name,
@@ -12383,7 +12357,8 @@ const buildMessage$22 = (childTagName) => `Your users get reshuffled HTML becaus
12383
12357
  const isParagraphElement = (candidate) => {
12384
12358
  if (!isNodeOfType(candidate, "JSXElement")) return false;
12385
12359
  const opening = candidate.openingElement;
12386
- return resolveJsxElementType(opening) === "p";
12360
+ if (!isNodeOfType(opening.name, "JSXIdentifier")) return false;
12361
+ return opening.name.name === "p";
12387
12362
  };
12388
12363
  const findEnclosingParagraph = (openingElement) => {
12389
12364
  const owningElement = openingElement.parent;
@@ -12404,7 +12379,8 @@ const htmlNoInvalidParagraphChild = defineRule({
12404
12379
  severity: "warn",
12405
12380
  recommendation: "Swap the `<p>` for a `<div>`, or move the child outside the paragraph.",
12406
12381
  create: (context) => ({ JSXOpeningElement(node) {
12407
- const childTagName = resolveJsxElementType(node);
12382
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
12383
+ const childTagName = node.name.name;
12408
12384
  if (!BLOCK_LEVEL_ELEMENTS.has(childTagName)) return;
12409
12385
  if (!findEnclosingParagraph(node)) return;
12410
12386
  context.report({
@@ -12435,7 +12411,7 @@ const getHostTagName = (jsxElement) => {
12435
12411
  if (!isNodeOfType(jsxElement, "JSXElement")) return null;
12436
12412
  const opening = jsxElement.openingElement;
12437
12413
  if (!isNodeOfType(opening.name, "JSXIdentifier")) return null;
12438
- const tagName = resolveJsxElementType(opening);
12414
+ const tagName = opening.name.name;
12439
12415
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return null;
12440
12416
  return tagName;
12441
12417
  };
@@ -12465,7 +12441,7 @@ const findClosestHostAncestor = (jsxElement) => {
12465
12441
  if (isNodeOfType(ancestor, "JSXElement")) {
12466
12442
  const opening = ancestor.openingElement;
12467
12443
  if (isNodeOfType(opening.name, "JSXIdentifier")) {
12468
- const ancestorTag = resolveJsxElementType(opening);
12444
+ const ancestorTag = opening.name.name;
12469
12445
  if (ancestorTag.length === 0) {
12470
12446
  previous = ancestor;
12471
12447
  ancestor = ancestor.parent ?? null;
@@ -12547,7 +12523,8 @@ const buildMessage$20 = (tagName) => `Your users get broken clicks, focus & scre
12547
12523
  const isJsxElementWithTagName = (candidate, tagName) => {
12548
12524
  if (!isNodeOfType(candidate, "JSXElement")) return false;
12549
12525
  const opening = candidate.openingElement;
12550
- return resolveJsxElementType(opening) === tagName;
12526
+ if (!isNodeOfType(opening.name, "JSXIdentifier")) return false;
12527
+ return opening.name.name === tagName;
12551
12528
  };
12552
12529
  const findEnclosingSameTag = (openingElement, tagName) => {
12553
12530
  const owningElement = openingElement.parent;
@@ -12568,7 +12545,8 @@ const htmlNoNestedInteractive = defineRule({
12568
12545
  severity: "warn",
12569
12546
  recommendation: "Move the inner `<a>` or `<button>` so it's a sibling, or change the outer one to a plain `<div>` or `<span>`.",
12570
12547
  create: (context) => ({ JSXOpeningElement(node) {
12571
- const tagName = resolveJsxElementType(node);
12548
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
12549
+ const tagName = node.name.name;
12572
12550
  if (tagName !== "a" && tagName !== "button") return;
12573
12551
  if (!findEnclosingSameTag(node, tagName)) return;
12574
12552
  context.report({
@@ -12683,7 +12661,7 @@ const iframeMissingSandbox = defineRule({
12683
12661
  matchByOccurrence: true,
12684
12662
  create: skipNonProductionFiles((context) => ({
12685
12663
  JSXOpeningElement(node) {
12686
- if (resolveJsxElementType(node) !== "iframe") return;
12664
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "iframe") return;
12687
12665
  const sandboxAttr = hasJsxPropIgnoreCase(node.attributes, "sandbox");
12688
12666
  if (!sandboxAttr) {
12689
12667
  const hasExplicitSrc = Boolean(hasJsxPropIgnoreCase(node.attributes, "src"));
@@ -14611,18 +14589,6 @@ const jsHoistRegexp = defineRule({
14611
14589
  }, { treatIteratorCallbacksAsLoops: true })
14612
14590
  });
14613
14591
  //#endregion
14614
- //#region src/plugin/utils/resolve-first-argument-binding.ts
14615
- const resolveFirstArgumentBinding = (firstParameter) => {
14616
- if (!firstParameter) return null;
14617
- if (!isNodeOfType(firstParameter, "RestElement")) return firstParameter;
14618
- if (!isNodeOfType(firstParameter.argument, "ArrayPattern")) return null;
14619
- const elements = firstParameter.argument.elements ?? [];
14620
- if (elements.length !== 1) return null;
14621
- const firstBinding = elements[0];
14622
- if (!firstBinding || isNodeOfType(firstBinding, "RestElement")) return null;
14623
- return firstBinding;
14624
- };
14625
- //#endregion
14626
14592
  //#region src/plugin/rules/js-performance/js-index-maps.ts
14627
14593
  const referencesParameter = (expression, parameterName) => {
14628
14594
  if (!expression) return false;
@@ -14633,7 +14599,7 @@ const referencesParameter = (expression, parameterName) => {
14633
14599
  const isSingleFieldEqualityPredicate = (node) => {
14634
14600
  const callback = node.arguments?.[0];
14635
14601
  if (!isInlineFunctionExpression(callback)) return false;
14636
- const firstParameter = resolveFirstArgumentBinding(callback.params?.[0]);
14602
+ const firstParameter = callback.params?.[0];
14637
14603
  if (!firstParameter || !isNodeOfType(firstParameter, "Identifier")) return false;
14638
14604
  let predicate = null;
14639
14605
  const body = callback.body;
@@ -15325,21 +15291,9 @@ const isSmallFixedListMember = (receiver) => {
15325
15291
  };
15326
15292
  const getResolvedInitializer = (receiver) => {
15327
15293
  if (!isNodeOfType(receiver, "Identifier")) return null;
15328
- const binding = findVariableInitializer(receiver, receiver.name);
15329
- const initializer = binding?.initializer ?? null;
15330
- if (!binding || !initializer) return null;
15331
- const isDefault = isNodeOfType(binding.bindingIdentifier.parent, "AssignmentPattern");
15332
- if (isNodeOfType(initializer, "Identifier")) {
15333
- const aliased = findVariableInitializer(initializer, initializer.name);
15334
- if (aliased?.initializer) return {
15335
- initializer: aliased.initializer,
15336
- isDefault: isDefault || isNodeOfType(aliased.bindingIdentifier.parent, "AssignmentPattern")
15337
- };
15338
- }
15339
- return {
15340
- initializer,
15341
- isDefault
15342
- };
15294
+ const initializer = findVariableInitializer(receiver, receiver.name)?.initializer ?? null;
15295
+ if (initializer && isNodeOfType(initializer, "Identifier")) return findVariableInitializer(initializer, initializer.name)?.initializer ?? initializer;
15296
+ return initializer;
15343
15297
  };
15344
15298
  const isSplitCall = (expression) => {
15345
15299
  if (!expression) return false;
@@ -15505,8 +15459,8 @@ const isBoundedConstantCollection = (collection) => {
15505
15459
  if (isScreamingSnakeCaseConstantReceiver(stripped)) return true;
15506
15460
  if (isSmallInlineLiteralArray(stripped)) return true;
15507
15461
  if (isNodeOfType(stripped, "Identifier")) {
15508
- const resolved = getResolvedInitializer(stripped);
15509
- if (resolved && !resolved.isDefault && isSmallInlineLiteralArray(resolved.initializer)) return true;
15462
+ const initializer = getResolvedInitializer(stripped);
15463
+ if (initializer && isSmallInlineLiteralArray(initializer)) return true;
15510
15464
  }
15511
15465
  return false;
15512
15466
  };
@@ -15558,8 +15512,8 @@ const jsSetMapLookups = defineRule({
15558
15512
  if (isIndexedArrayElementWithStringArgument(receiver, node.arguments?.[0])) return;
15559
15513
  const resolvedInitializer = getResolvedInitializer(receiver);
15560
15514
  if (resolvedInitializer) {
15561
- if (isLikelyStringReceiver(resolvedInitializer.initializer)) return;
15562
- if (!resolvedInitializer.isDefault && isSmallInlineLiteralArray(resolvedInitializer.initializer)) return;
15515
+ if (isLikelyStringReceiver(resolvedInitializer)) return;
15516
+ if (isSmallInlineLiteralArray(resolvedInitializer)) return;
15563
15517
  }
15564
15518
  if (isStringElementOfSplitIteration(receiver)) return;
15565
15519
  if (isReceiverDeclaredInNearestLoop(receiver, node)) return;
@@ -15972,21 +15926,14 @@ const jsxFilenameExtension = defineRule({
15972
15926
  });
15973
15927
  //#endregion
15974
15928
  //#region src/plugin/utils/is-jsx-fragment-element.ts
15975
- const isJsxFragmentElement = (node, scopes) => {
15929
+ const isJsxFragmentElement = (node) => {
15976
15930
  if (!isNodeOfType(node, "JSXOpeningElement")) return false;
15977
15931
  const elementName = node.name;
15978
- if (isNodeOfType(elementName, "JSXIdentifier")) {
15979
- if (!scopes) return elementName.name === "Fragment";
15980
- const symbol = resolveConstIdentifierAlias(elementName, scopes);
15981
- if (!symbol) return elementName.name === "Fragment" && scopes.isGlobalReference(elementName);
15982
- return isImportedFromReact(symbol) && getImportedName(symbol.declarationNode) === "Fragment";
15983
- }
15932
+ if (isNodeOfType(elementName, "JSXIdentifier")) return elementName.name === "Fragment";
15984
15933
  if (isNodeOfType(elementName, "JSXMemberExpression")) {
15985
15934
  if (!isNodeOfType(elementName.object, "JSXIdentifier")) return false;
15986
- if (elementName.property.name !== "Fragment") return false;
15987
- if (!scopes) return elementName.object.name === "React";
15988
- if (isReactNamespaceImport(elementName.object, scopes)) return true;
15989
- return elementName.object.name === "React" && scopes.isGlobalReference(elementName.object);
15935
+ if (elementName.object.name !== "React") return false;
15936
+ return elementName.property.name === "Fragment";
15990
15937
  }
15991
15938
  return false;
15992
15939
  };
@@ -16012,7 +15959,7 @@ const jsxFragments = defineRule({
16012
15959
  if (mode !== "syntax") return;
16013
15960
  if (!node.closingElement) return;
16014
15961
  const openingElement = node.openingElement;
16015
- if (!isJsxFragmentElement(openingElement, context.scopes)) return;
15962
+ if (!isJsxFragmentElement(openingElement)) return;
16016
15963
  if (openingElement.attributes.length > 0) return;
16017
15964
  context.report({
16018
15965
  node: openingElement,
@@ -18693,6 +18640,10 @@ const resolveSettings$29 = (settings) => {
18693
18640
  if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
18694
18641
  return reactDoctor.jsxNoScriptUrl ?? {};
18695
18642
  };
18643
+ const getElementName = (node) => {
18644
+ if (isNodeOfType(node.name, "JSXIdentifier")) return node.name.name;
18645
+ return null;
18646
+ };
18696
18647
  const isLinkPropForElement = (elementName, attributeName, options) => {
18697
18648
  if (elementName === "a" && attributeName === "href") return true;
18698
18649
  const explicit = options.components?.[elementName];
@@ -18712,8 +18663,7 @@ const jsxNoScriptUrl = defineRule({
18712
18663
  create: (context) => {
18713
18664
  const options = resolveSettings$29(context.settings);
18714
18665
  return { JSXOpeningElement(node) {
18715
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
18716
- const elementName = resolveJsxElementType(node);
18666
+ const elementName = getElementName(node);
18717
18667
  if (!elementName) return;
18718
18668
  for (const attribute of node.attributes) {
18719
18669
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
@@ -18900,6 +18850,11 @@ const checkTarget = (attributeValue) => {
18900
18850
  alternate: false
18901
18851
  };
18902
18852
  };
18853
+ const getOpeningElementName = (node) => {
18854
+ const name = node.name;
18855
+ if (isNodeOfType(name, "JSXIdentifier")) return name.name;
18856
+ return null;
18857
+ };
18903
18858
  const jsxNoTargetBlank = defineRule({
18904
18859
  id: "jsx-no-target-blank",
18905
18860
  title: "Unsafe target=_blank link",
@@ -18919,8 +18874,7 @@ const jsxNoTargetBlank = defineRule({
18919
18874
  return settings.formComponents.has(tagName);
18920
18875
  };
18921
18876
  return { JSXOpeningElement(node) {
18922
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
18923
- const tagName = resolveJsxElementType(node);
18877
+ const tagName = getOpeningElementName(node);
18924
18878
  if (!tagName) return;
18925
18879
  if (!isLink(tagName) && !isForm(tagName)) return;
18926
18880
  const linkAttributeNames = settings.linkComponents.get(tagName) ?? ["href"];
@@ -19153,7 +19107,7 @@ const jsxNoUselessFragment = defineRule({
19153
19107
  return {
19154
19108
  JSXElement(node) {
19155
19109
  const openingElement = node.openingElement;
19156
- if (!isJsxFragmentElement(openingElement, context.scopes)) return;
19110
+ if (!isJsxFragmentElement(openingElement)) return;
19157
19111
  if (hasJsxKeyAttribute(openingElement)) return;
19158
19112
  if (checkChildren(node, openingElement, node.children)) return;
19159
19113
  if (isChildOfHtmlElement(node)) context.report({
@@ -20395,6 +20349,9 @@ const hasDirective = (programNode, directive) => {
20395
20349
  return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
20396
20350
  };
20397
20351
  //#endregion
20352
+ //#region src/plugin/utils/is-uppercase-name.ts
20353
+ const isUppercaseName = (name) => UPPERCASE_PATTERN.test(name);
20354
+ //#endregion
20398
20355
  //#region src/plugin/utils/is-component-assignment.ts
20399
20356
  const isComponentAssignment = (node) => isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && isUppercaseName(node.id.name) && Boolean(node.init) && (isNodeOfType(node.init, "ArrowFunctionExpression") || isNodeOfType(node.init, "FunctionExpression"));
20400
20357
  //#endregion
@@ -20727,7 +20684,7 @@ const nextjsNoAElement = defineRule({
20727
20684
  severity: "warn",
20728
20685
  recommendation: "`import Link from 'next/link'` for client-side navigation, prefetching, and preserved scroll position",
20729
20686
  create: (context) => ({ JSXOpeningElement(node) {
20730
- if (resolveJsxElementType(node) !== "a") return;
20687
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
20731
20688
  const attributes = node.attributes ?? [];
20732
20689
  const downloadAttribute = findJsxAttribute(attributes, "download");
20733
20690
  if (downloadAttribute) {
@@ -20923,7 +20880,7 @@ const nextjsNoCssLink = defineRule({
20923
20880
  severity: "warn",
20924
20881
  recommendation: "Import CSS directly or use CSS Modules so Next.js can bundle, order, and optimize the stylesheet.",
20925
20882
  create: (context) => ({ JSXOpeningElement(node) {
20926
- if (resolveJsxElementType(node) !== "link") return;
20883
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "link") return;
20927
20884
  const attributes = node.attributes ?? [];
20928
20885
  const relAttribute = findJsxAttribute(attributes, "rel");
20929
20886
  if (!relAttribute?.value) return;
@@ -21031,7 +20988,7 @@ const nextjsNoFontLink = defineRule({
21031
20988
  severity: "warn",
21032
20989
  recommendation: "`import { Inter } from \"next/font/google\"` for self-hosting, zero layout shift, and no render-blocking requests",
21033
20990
  create: (context) => ({ JSXOpeningElement(node) {
21034
- if (resolveJsxElementType(node) !== "link") return;
20991
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "link") return;
21035
20992
  const attributes = node.attributes ?? [];
21036
20993
  const hrefAttribute = findJsxAttribute(attributes, "href");
21037
20994
  if (!hrefAttribute?.value) return;
@@ -21206,7 +21163,7 @@ const nextjsNoImgElement = defineRule({
21206
21163
  create: (context) => {
21207
21164
  if (isGeneratedImageRenderContext(context)) return {};
21208
21165
  return { JSXOpeningElement(node) {
21209
- if (resolveJsxElementType(node) !== "img") return;
21166
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "img") return;
21210
21167
  if (isGeneratedImageRenderContext(context, node)) return;
21211
21168
  const programRoot = findProgramRoot(node);
21212
21169
  if (programRoot && hasEmailTemplateImport(programRoot)) return;
@@ -21253,8 +21210,8 @@ const nextjsNoPolyfillScript = defineRule({
21253
21210
  severity: "warn",
21254
21211
  recommendation: "Next.js includes polyfills for fetch, Promise, Object.assign, Array.from, and 50+ others automatically",
21255
21212
  create: (context) => ({ JSXOpeningElement(node) {
21256
- const elementName = resolveJsxElementType(node);
21257
- if (elementName !== "script" && elementName !== "Script") return;
21213
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
21214
+ if (node.name.name !== "script" && node.name.name !== "Script") return;
21258
21215
  const srcAttribute = findJsxAttribute(node.attributes ?? [], "src");
21259
21216
  if (!srcAttribute?.value) return;
21260
21217
  const srcValue = isNodeOfType(srcAttribute.value, "Literal") ? srcAttribute.value.value : null;
@@ -23062,15 +23019,10 @@ const isProp = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
23062
23019
  if (!declaringNode) return false;
23063
23020
  return isReactFunctionalComponent(declaringNode) && !isReactFunctionalHOC(analysis, declaringNode) || isCustomHook(declaringNode);
23064
23021
  }));
23065
- const isWholePropsParameterBinding = (bindingNode) => {
23066
- if (isFunctionLike$1(bindingNode.parent)) return true;
23067
- let declaringFunction = bindingNode.parent;
23068
- while (declaringFunction && !isFunctionLike$1(declaringFunction)) declaringFunction = declaringFunction.parent;
23069
- return Boolean(declaringFunction && resolveFirstArgumentBinding(declaringFunction.params?.[0]) === bindingNode);
23070
- };
23071
23022
  const isWholePropsObjectReference = (analysis, ref) => isProp(analysis, ref) && Boolean(ref.resolved?.defs.some((def) => {
23072
23023
  if (def.type !== "Parameter") return false;
23073
- return isWholePropsParameterBinding(def.name);
23024
+ const bindingParent = def.name.parent;
23025
+ return isFunctionLike$1(bindingParent);
23074
23026
  }));
23075
23027
  const isIdentifierOrMemberExpression = (node) => isNodeOfType(node, "Identifier") || isNodeOfType(node, "MemberExpression");
23076
23028
  const isPropAlias = (analysis, ref) => {
@@ -26067,56 +26019,6 @@ const noBarrelImport = defineRule({
26067
26019
  }
26068
26020
  });
26069
26021
  //#endregion
26070
- //#region src/plugin/utils/function-returns-matching-expression.ts
26071
- const collectReturnedExpressions = (functionNode) => {
26072
- if (!isFunctionLike$1(functionNode)) return [];
26073
- const body = functionNode.body;
26074
- if (!body) return [];
26075
- if (!isNodeOfType(body, "BlockStatement")) return [body];
26076
- const returnedExpressions = [];
26077
- walkAst(body, (node) => {
26078
- if (node !== body && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
26079
- if (isNodeOfType(node, "ReturnStatement") && node.argument) returnedExpressions.push(node.argument);
26080
- });
26081
- return returnedExpressions;
26082
- };
26083
- const functionReturnsMatchingExpression = (functionNode, scopes, matchesExpression) => {
26084
- const visitedExpressions = /* @__PURE__ */ new Set();
26085
- const visitedFunctions = /* @__PURE__ */ new Set();
26086
- const functionMatches = (candidateFunction) => {
26087
- if (visitedFunctions.has(candidateFunction)) return false;
26088
- visitedFunctions.add(candidateFunction);
26089
- return collectReturnedExpressions(candidateFunction).some(expressionMatches);
26090
- };
26091
- const expressionMatches = (expression) => {
26092
- const unwrappedExpression = stripParenExpression(expression);
26093
- if (visitedExpressions.has(unwrappedExpression)) return false;
26094
- visitedExpressions.add(unwrappedExpression);
26095
- if (matchesExpression(unwrappedExpression)) return true;
26096
- if (isNodeOfType(unwrappedExpression, "Identifier")) {
26097
- const symbol = scopes.symbolFor(unwrappedExpression);
26098
- if (!symbol || symbol.kind !== "const" || !symbol.initializer) return false;
26099
- const initializer = stripParenExpression(symbol.initializer);
26100
- if (isFunctionLike$1(initializer)) return false;
26101
- return expressionMatches(initializer);
26102
- }
26103
- if (isNodeOfType(unwrappedExpression, "CallExpression")) {
26104
- if (unwrappedExpression.arguments.length !== 0) return false;
26105
- if (!isNodeOfType(unwrappedExpression.callee, "Identifier")) return false;
26106
- const symbol = scopes.symbolFor(unwrappedExpression.callee);
26107
- if (!symbol || symbol.kind !== "const" && symbol.kind !== "function") return false;
26108
- const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
26109
- const candidateFunction = isFunctionLike$1(initializer) ? initializer : isFunctionLike$1(symbol.declarationNode) ? symbol.declarationNode : null;
26110
- if (!candidateFunction || candidateFunction.async || candidateFunction.generator || candidateFunction.params.length !== 0) return false;
26111
- return functionMatches(candidateFunction);
26112
- }
26113
- if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return expressionMatches(unwrappedExpression.consequent) || expressionMatches(unwrappedExpression.alternate);
26114
- if (isNodeOfType(unwrappedExpression, "LogicalExpression")) return expressionMatches(unwrappedExpression.left) || expressionMatches(unwrappedExpression.right);
26115
- return false;
26116
- };
26117
- return functionMatches(functionNode);
26118
- };
26119
- //#endregion
26120
26022
  //#region src/plugin/utils/function-contains-react-render-output.ts
26121
26023
  const NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES = new Set([
26122
26024
  "FunctionDeclaration",
@@ -26136,13 +26038,12 @@ const isCallArgumentFunctionExpression = (node) => {
26136
26038
  return parent.arguments.some((argumentNode) => argumentNode === node);
26137
26039
  };
26138
26040
  const isNestedRenderEvidenceBoundary = (node) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isCallArgumentFunctionExpression(node);
26139
- const isRenderOutputExpression = (node, scopes) => node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS);
26140
26041
  const containsRenderOutput$1 = (rootNode, scopes) => {
26141
26042
  let hasRenderOutput = false;
26142
26043
  walkAst(rootNode, (node) => {
26143
26044
  if (hasRenderOutput) return false;
26144
26045
  if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
26145
- if (isRenderOutputExpression(node, scopes)) {
26046
+ if (node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS)) {
26146
26047
  hasRenderOutput = true;
26147
26048
  return false;
26148
26049
  }
@@ -26153,7 +26054,7 @@ const renderOutputCache = /* @__PURE__ */ new WeakMap();
26153
26054
  const functionContainsReactRenderOutput = (functionNode, scopes) => {
26154
26055
  const cachedEntry = renderOutputCache.get(functionNode);
26155
26056
  if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
26156
- const hasRenderOutput = containsRenderOutput$1(functionNode, scopes) || functionReturnsMatchingExpression(functionNode, scopes, (expression) => isRenderOutputExpression(expression, scopes));
26057
+ const hasRenderOutput = containsRenderOutput$1(functionNode, scopes);
26157
26058
  renderOutputCache.set(functionNode, {
26158
26059
  scopes,
26159
26060
  hasRenderOutput
@@ -28618,7 +28519,7 @@ const noDisabledZoom = defineRule({
28618
28519
  category: "Accessibility",
28619
28520
  recommendation: "Remove `user-scalable=no` and `maximum-scale` from the viewport meta tag. If the layout breaks at 200% zoom, fix the layout instead.",
28620
28521
  create: (context) => ({ JSXOpeningElement(node) {
28621
- if (resolveJsxElementType(node) !== "meta") return;
28522
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "meta") return;
28622
28523
  const nameAttr = findJsxAttribute(node.attributes ?? [], "name");
28623
28524
  if (!nameAttr?.value) return;
28624
28525
  if ((isNodeOfType(nameAttr.value, "Literal") ? nameAttr.value.value : null) !== "viewport") return;
@@ -31790,7 +31691,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
31790
31691
  severity: "warn",
31791
31692
  recommendation: "Don't combine `loading=\"lazy\"` with `fetchPriority=\"high\"`. A high-priority image (usually the LCP) should load eagerly; a lazy image is by definition not high priority.",
31792
31693
  create: (context) => ({ JSXOpeningElement(node) {
31793
- if (resolveJsxElementType(node) !== "img") return;
31694
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "img") return;
31794
31695
  const loadingAttribute = hasJsxPropIgnoreCase(node.attributes, "loading");
31795
31696
  if (!loadingAttribute || getJsxPropStringValue(loadingAttribute)?.toLowerCase() !== "lazy") return;
31796
31697
  const fetchPriorityAttribute = hasJsxPropIgnoreCase(node.attributes, "fetchPriority");
@@ -32031,7 +31932,7 @@ const noIndeterminateAttribute = defineRule({
32031
31932
  if (classifyReactNativeFileTarget(context) === "react-native") return {};
32032
31933
  return {
32033
31934
  JSXOpeningElement(node) {
32034
- if (resolveJsxElementType(node) !== "input") return;
31935
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "input") return;
32035
31936
  let typeAttribute = null;
32036
31937
  let typeAttributeIndex = null;
32037
31938
  let indeterminateAttribute = null;
@@ -33103,13 +33004,11 @@ const noManyBooleanProps = defineRule({
33103
33004
  };
33104
33005
  const checkComponent = (functionNode, param, body, componentName, reportNode) => {
33105
33006
  if (!param) return;
33106
- const propsBinding = resolveFirstArgumentBinding(param);
33107
- if (!propsBinding) return;
33108
33007
  if (!functionContainsReactRenderOutput(functionNode, context.scopes)) return;
33109
- if (isNodeOfType(propsBinding, "ObjectPattern")) {
33008
+ if (isNodeOfType(param, "ObjectPattern")) {
33110
33009
  const callbackUsedNames = collectCallbackUsedNames(body, param, context.scopes);
33111
33010
  const booleanLikePropNames = [];
33112
- for (const property of propsBinding.properties ?? []) {
33011
+ for (const property of param.properties ?? []) {
33113
33012
  if (!isNodeOfType(property, "Property")) continue;
33114
33013
  const keyName = isNodeOfType(property.key, "Identifier") ? property.key.name : null;
33115
33014
  if (!keyName) continue;
@@ -33120,7 +33019,7 @@ const noManyBooleanProps = defineRule({
33120
33019
  reportIfMany(booleanLikePropNames, componentName, reportNode);
33121
33020
  return;
33122
33021
  }
33123
- if (isNodeOfType(propsBinding, "Identifier")) reportIfMany([...collectBooleanLikePropsFromBody(body, propsBinding.name)], componentName, reportNode);
33022
+ if (isNodeOfType(param, "Identifier")) reportIfMany([...collectBooleanLikePropsFromBody(body, param.name)], componentName, reportNode);
33124
33023
  };
33125
33024
  return {
33126
33025
  FunctionDeclaration(node) {
@@ -35872,7 +35771,7 @@ const noPreventDefault = defineRule({
35872
35771
  const isServerActionsFramework = hasCapability(context.settings, "server-actions");
35873
35772
  const formMessage = isServerActionsFramework ? FORM_MESSAGE_SERVER_CAPABLE : FORM_MESSAGE_GENERIC;
35874
35773
  return { JSXOpeningElement(node) {
35875
- const elementName = resolveJsxElementType(node);
35774
+ const elementName = isNodeOfType(node.name, "JSXIdentifier") ? node.name.name : null;
35876
35775
  if (!elementName) return;
35877
35776
  const targetEventProps = PREVENT_DEFAULT_ELEMENTS.get(elementName);
35878
35777
  if (!targetEventProps) return;
@@ -38336,10 +38235,9 @@ const noStringFalseOnBooleanAttribute = defineRule({
38336
38235
  recommendation: "Use the boolean form on boolean attributes: `disabled` / `disabled={true}` / `disabled={false}`, not `disabled=\"false\"`. A non-empty string is truthy, so `=\"false\"` actually turns the attribute ON.",
38337
38236
  create: (context) => ({ JSXOpeningElement(node) {
38338
38237
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
38339
- const elementName = resolveJsxElementType(node);
38340
- const firstCharacter = elementName.charCodeAt(0);
38238
+ const firstCharacter = node.name.name.charCodeAt(0);
38341
38239
  if (firstCharacter < 97 || firstCharacter > 122) return;
38342
- if (elementName.includes("-")) return;
38240
+ if (node.name.name.includes("-")) return;
38343
38241
  for (const attribute of node.attributes) {
38344
38242
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
38345
38243
  if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
@@ -38727,7 +38625,8 @@ const noUncontrolledInput = defineRule({
38727
38625
  const undefinedInitialStateNames = isNodeOfType(componentBody, "BlockStatement") ? collectUndefinedInitialStateNames(componentBody) : /* @__PURE__ */ new Set();
38728
38626
  walkAst(componentBody, (child) => {
38729
38627
  if (!isNodeOfType(child, "JSXOpeningElement")) return;
38730
- const tagName = resolveJsxElementType(child);
38628
+ if (!isNodeOfType(child.name, "JSXIdentifier")) return;
38629
+ const tagName = child.name.name;
38731
38630
  if (!UNCONTROLLED_INPUT_TAGS.has(tagName)) return;
38732
38631
  const attributes = child.attributes ?? [];
38733
38632
  if (hasJsxSpreadAttribute(attributes)) return;
@@ -38788,7 +38687,7 @@ const noUndeferredThirdParty = defineRule({
38788
38687
  severity: "warn",
38789
38688
  recommendation: "Use `next/script` with `strategy=\"lazyOnload\"`, or add the `defer` attribute.",
38790
38689
  create: (context) => ({ JSXOpeningElement(node) {
38791
- if (resolveJsxElementType(node) !== "script") return;
38690
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "script") return;
38792
38691
  const attributes = node.attributes ?? [];
38793
38692
  const srcAttribute = findJsxAttribute(attributes, "src");
38794
38693
  if (!srcAttribute) return;
@@ -40003,7 +39902,7 @@ const noUnknownProperty = defineRule({
40003
39902
  }
40004
39903
  if (fileIsNonReactJsx) return;
40005
39904
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
40006
- const elementType = resolveJsxElementType(node);
39905
+ const elementType = node.name.name;
40007
39906
  const firstCharacter = elementType.charCodeAt(0);
40008
39907
  if (!(firstCharacter >= 97 && firstCharacter <= 122) || elementType === "fbt" || elementType === "fbs") return;
40009
39908
  let isValidHtmlTag = isKnownDomTag(elementType);
@@ -40181,21 +40080,20 @@ const expressionContainsJsxOrCreateElement = (root) => {
40181
40080
  });
40182
40081
  return found;
40183
40082
  };
40184
- const functionContainsJsxOrCreateElement = (functionNode, scopes) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement);
40185
40083
  const isReactClassComponent = (classNode) => {
40186
40084
  if (isEs6Component(classNode)) return true;
40187
40085
  return expressionContainsJsxOrCreateElement(classNode);
40188
40086
  };
40189
- const findEnclosingComponent = (node, scopes) => {
40087
+ const findEnclosingComponent = (node) => {
40190
40088
  let walker = node.parent;
40191
40089
  while (walker) {
40192
40090
  if (isFunctionLike$1(walker)) {
40193
40091
  const componentName = inferFunctionLikeName(walker);
40194
- if (componentName && isReactComponentName(componentName) && functionContainsJsxOrCreateElement(walker, scopes)) return {
40092
+ if (componentName && isReactComponentName(componentName) && expressionContainsJsxOrCreateElement(walker)) return {
40195
40093
  component: walker,
40196
40094
  name: componentName
40197
40095
  };
40198
- if (!componentName && functionContainsJsxOrCreateElement(walker, scopes) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
40096
+ if (!componentName && expressionContainsJsxOrCreateElement(walker) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
40199
40097
  component: walker,
40200
40098
  name: null
40201
40099
  };
@@ -40434,7 +40332,7 @@ const noUnstableNestedComponents = defineRule({
40434
40332
  if (renderPropRegex.test(propInfo.propName)) return;
40435
40333
  if (settings.allowAsProps) return;
40436
40334
  }
40437
- const enclosing = findEnclosingComponent(candidateNode, context.scopes);
40335
+ const enclosing = findEnclosingComponent(candidateNode);
40438
40336
  if (!enclosing) return;
40439
40337
  const gatedName = propInfo ? null : requiredInstantiationName;
40440
40338
  queuedReports.push({
@@ -40445,7 +40343,7 @@ const noUnstableNestedComponents = defineRule({
40445
40343
  });
40446
40344
  };
40447
40345
  const checkFunctionLike = (node) => {
40448
- if (!functionContainsJsxOrCreateElement(node, context.scopes)) return;
40346
+ if (!expressionContainsJsxOrCreateElement(node)) return;
40449
40347
  const inferredName = inferFunctionLikeName(node);
40450
40348
  const propInfo = isComponentDeclaredInProp(node);
40451
40349
  const isObjectCallback = isObjectCallbackCandidate(node);
@@ -41705,7 +41603,7 @@ const preactPreferOndblclick = defineRule({
41705
41603
  recommendation: "Rename `onDoubleClick` to `onDblClick` because Preact core listens for the DOM `dblclick` event name and `onDoubleClick` never fires.",
41706
41604
  create: (context) => ({ JSXOpeningElement(node) {
41707
41605
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
41708
- const tagName = resolveJsxElementType(node);
41606
+ const tagName = node.name.name;
41709
41607
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return;
41710
41608
  const onDoubleClickAttribute = findJsxAttribute(node.attributes, "onDoubleClick");
41711
41609
  if (!onDoubleClickAttribute) return;
@@ -41725,7 +41623,7 @@ const COMPAT_EXEMPT_INPUT_TYPES = new Set([
41725
41623
  ]);
41726
41624
  const isTextLikeInput = (openingElement) => {
41727
41625
  if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
41728
- const tagName = resolveJsxElementType(openingElement);
41626
+ const tagName = openingElement.name.name;
41729
41627
  if (tagName === "textarea") return true;
41730
41628
  if (tagName !== "input") return false;
41731
41629
  const typeAttribute = findJsxAttribute(openingElement.attributes, "type");
@@ -41882,9 +41780,8 @@ const CROSS_CUTTING_STATE_BOOLEAN_NAMES = new Set([
41882
41780
  ]);
41883
41781
  const collectBooleanPropBindings = (param) => {
41884
41782
  const bindings = /* @__PURE__ */ new Set();
41885
- const propsBinding = resolveFirstArgumentBinding(param);
41886
- if (!isNodeOfType(propsBinding, "ObjectPattern")) return bindings;
41887
- for (const property of propsBinding.properties ?? []) {
41783
+ if (!param || !isNodeOfType(param, "ObjectPattern")) return bindings;
41784
+ for (const property of param.properties ?? []) {
41888
41785
  if (!isNodeOfType(property, "Property")) continue;
41889
41786
  if (property.computed) continue;
41890
41787
  if (!isNodeOfType(property.key, "Identifier")) continue;
@@ -42145,7 +42042,7 @@ const preferHtmlDialog = defineRule({
42145
42042
  },
42146
42043
  JSXOpeningElement(node) {
42147
42044
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
42148
- const tagName = resolveJsxElementType(node);
42045
+ const tagName = node.name.name;
42149
42046
  if (tagName === "dialog") return;
42150
42047
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return;
42151
42048
  if (!HTML_TAGS.has(tagName)) return;
@@ -44182,7 +44079,7 @@ const renderingAnimateSvgWrapper = defineRule({
44182
44079
  severity: "warn",
44183
44080
  recommendation: "Wrap the SVG in a motion element so animation props apply to a stable wrapper instead of the SVG node itself.",
44184
44081
  create: (context) => ({ JSXOpeningElement(node) {
44185
- if (resolveJsxElementType(node) !== "svg") return;
44082
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "svg") return;
44186
44083
  if (node.attributes?.some((attribute) => isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && MOTION_ANIMATE_PROPS.has(attribute.name.name))) context.report({
44187
44084
  node,
44188
44085
  message: "This is slow to render because you animate <svg> directly, so wrap it in a <div> or <motion.div> & animate that instead"
@@ -45475,10 +45372,14 @@ const rerenderLazyStateInit = defineRule({
45475
45372
  //#endregion
45476
45373
  //#region src/plugin/rules/performance/rerender-memo-before-early-return.ts
45477
45374
  const isJsxExpression = (node) => Boolean(node && isJsxElementOrFragment(stripParenExpression(node)));
45478
- const callbackReturnsJsx = (callback, scopes) => {
45375
+ const callbackReturnsJsx = (callback) => {
45479
45376
  if (!callback) return false;
45480
45377
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
45481
- return functionReturnsMatchingExpression(callback, scopes, isJsxExpression);
45378
+ const body = callback.body;
45379
+ if (isJsxExpression(body)) return true;
45380
+ if (!isNodeOfType(body, "BlockStatement")) return false;
45381
+ for (const stmt of body.body ?? []) if (isNodeOfType(stmt, "ReturnStatement") && isJsxExpression(stmt.argument)) return true;
45382
+ return false;
45482
45383
  };
45483
45384
  const returnArgumentUsesAnyName = (returnStatement, names) => {
45484
45385
  if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
@@ -45556,7 +45457,7 @@ const rerenderMemoBeforeEarlyReturn = defineRule({
45556
45457
  if (!isNodeOfType(stmt, "VariableDeclaration")) continue;
45557
45458
  for (const declarator of stmt.declarations ?? []) {
45558
45459
  const init = declarator.init;
45559
- if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0], context.scopes)) {
45460
+ if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0])) {
45560
45461
  memoNode = declarator;
45561
45462
  callbackGuardTests = collectLeadingCallbackGuardTests(init.arguments?.[0]);
45562
45463
  if (isNodeOfType(declarator.id, "Identifier")) memoConsumerNames.add(declarator.id.name);
@@ -45622,11 +45523,8 @@ const collectFromObjectPattern = (pattern, bindings) => {
45622
45523
  const collectDefaultedEmptyBindings = (functionNode) => {
45623
45524
  const bindings = /* @__PURE__ */ new Map();
45624
45525
  const params = functionNode.params ?? [];
45625
- for (const [parameterIndex, param] of params.entries()) {
45626
- const parameterBinding = parameterIndex === 0 ? resolveFirstArgumentBinding(param) : param;
45627
- if (parameterBinding) collectFromObjectPattern(parameterBinding, bindings);
45628
- }
45629
- const propsParam = resolveFirstArgumentBinding(params[0]);
45526
+ for (const param of params) collectFromObjectPattern(param, bindings);
45527
+ const propsParam = params[0];
45630
45528
  const body = functionNode.body;
45631
45529
  if (!propsParam || !isNodeOfType(propsParam, "Identifier") || !body) return bindings;
45632
45530
  if (!isNodeOfType(body, "BlockStatement")) return bindings;
@@ -53740,6 +53638,10 @@ const isInvalidStyleExpression = (expression) => {
53740
53638
  }
53741
53639
  return false;
53742
53640
  };
53641
+ const getJsxOpeningElementName = (node) => {
53642
+ if (isNodeOfType(node.name, "JSXIdentifier")) return node.name.name;
53643
+ return null;
53644
+ };
53743
53645
  const stylePropObject = defineRule({
53744
53646
  id: "style-prop-object",
53745
53647
  title: "Style prop is not an object",
@@ -53751,8 +53653,7 @@ const stylePropObject = defineRule({
53751
53653
  const allowSet = new Set(allow);
53752
53654
  return {
53753
53655
  JSXOpeningElement(node) {
53754
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
53755
- const elementName = resolveJsxElementType(node);
53656
+ const elementName = getJsxOpeningElementName(node);
53756
53657
  if (elementName && allowSet.has(elementName)) return;
53757
53658
  if (elementName) {
53758
53659
  const firstCharCode = elementName.charCodeAt(0);
@@ -54457,7 +54358,7 @@ const tanstackStartNoAnchorElement = defineRule({
54457
54358
  create: (context) => {
54458
54359
  if (!isInProjectDirectory(context, "routes")) return {};
54459
54360
  return { JSXOpeningElement(node) {
54460
- if (resolveJsxElementType(node) !== "a") return;
54361
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
54461
54362
  const attributes = node.attributes ?? [];
54462
54363
  const hrefAttribute = findJsxAttribute(attributes, "href");
54463
54364
  if (!hrefAttribute?.value) return;
@@ -55078,7 +54979,8 @@ const voidDomElementsNoChildren = defineRule({
55078
54979
  create: (context) => ({
55079
54980
  JSXElement(node) {
55080
54981
  const openingElement = node.openingElement;
55081
- const tagName = resolveJsxElementType(openingElement);
54982
+ if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return;
54983
+ const tagName = openingElement.name.name;
55082
54984
  if (!VOID_DOM_ELEMENTS.has(tagName)) return;
55083
54985
  const hasChildrenContent = node.children.some(isMeaningfulJsxChild);
55084
54986
  const hasChildrenLikeProp = findChildrenLikePropName(openingElement.attributes);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.6-dev.eb09902",
3
+ "version": "0.7.6-dev.fbd85e3",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",