oxlint-plugin-react-doctor 0.7.6-dev.64950ba → 0.7.6-dev.778f1a2

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 +185 -99
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1518,6 +1518,37 @@ 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
1521
1552
  //#region src/plugin/utils/get-element-type.ts
1522
1553
  const EMPTY_JSX_A11Y_SETTINGS = Object.freeze({});
1523
1554
  const jsxA11ySettingsCache = /* @__PURE__ */ new WeakMap();
@@ -1530,14 +1561,8 @@ const readJsxA11ySettings = (settings) => {
1530
1561
  jsxA11ySettingsCache.set(settings, a11ySettings);
1531
1562
  return a11ySettings;
1532
1563
  };
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
- };
1539
1564
  const computeElementType = (openingElement, a11ySettings) => {
1540
- const baseName = flattenJsxName$2(openingElement.name);
1565
+ const baseName = resolveJsxElementType(openingElement);
1541
1566
  if (a11ySettings.polymorphicPropName) {
1542
1567
  const polymorphicAttribute = hasJsxPropIgnoreCase(openingElement.attributes, a11ySettings.polymorphicPropName);
1543
1568
  if (polymorphicAttribute) {
@@ -2372,8 +2397,9 @@ const anchorIsValid = defineRule({
2372
2397
  const isTestlikeFile = isTestlikeFilename(context.filename);
2373
2398
  return { JSXOpeningElement(node) {
2374
2399
  if (isTestlikeFile) return;
2375
- if (!fileHasJsxA11ySettings && (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a")) return;
2376
- if (getElementType(node, context.settings) !== "a") return;
2400
+ const tag = getElementType(node, context.settings);
2401
+ if (!fileHasJsxA11ySettings && tag !== "a") return;
2402
+ if (tag !== "a") return;
2377
2403
  let hrefAttribute;
2378
2404
  for (const attributeName of settings.hrefAttributeNames) {
2379
2405
  hrefAttribute = hasJsxPropIgnoreCase(node.attributes, attributeName);
@@ -5728,7 +5754,7 @@ const buttonHasType = defineRule({
5728
5754
  return {
5729
5755
  JSXOpeningElement(node) {
5730
5756
  if (isTestlikeFile) return;
5731
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "button") return;
5757
+ if (resolveJsxElementType(node) !== "button") return;
5732
5758
  const typeAttr = hasJsxPropIgnoreCase(node.attributes, "type");
5733
5759
  if (!typeAttr) {
5734
5760
  if (hasJsxSpreadAttribute$1(node.attributes)) {
@@ -5918,7 +5944,7 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
5918
5944
  };
5919
5945
  return {
5920
5946
  JSXOpeningElement(node) {
5921
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "input") return;
5947
+ if (resolveJsxElementType(node) !== "input") return;
5922
5948
  reportFromPresence(collectFromJsxAttributes(node.attributes));
5923
5949
  },
5924
5950
  CallExpression(node) {
@@ -5995,7 +6021,7 @@ const isPureEventBlockerHandler = (attribute) => {
5995
6021
  //#endregion
5996
6022
  //#region src/plugin/utils/resolve-const-identifier-alias.ts
5997
6023
  const resolveConstIdentifierAlias = (identifier, scopes) => {
5998
- if (!isNodeOfType(identifier, "Identifier")) return null;
6024
+ if (!isNodeOfType(identifier, "Identifier") && !isNodeOfType(identifier, "JSXIdentifier")) return null;
5999
6025
  const visitedSymbolIds = /* @__PURE__ */ new Set();
6000
6026
  let symbol = scopes.symbolFor(identifier);
6001
6027
  while (symbol?.kind === "const") {
@@ -7433,7 +7459,7 @@ const findJsxAttribute = (attributes, attributeName) => attributes?.find((attrib
7433
7459
  const getOpeningElementTagName = (openingElement) => {
7434
7460
  if (!openingElement) return null;
7435
7461
  if (!isNodeOfType(openingElement, "JSXOpeningElement")) return null;
7436
- if (isNodeOfType(openingElement.name, "JSXIdentifier")) return openingElement.name.name;
7462
+ if (isNodeOfType(openingElement.name, "JSXIdentifier")) return resolveJsxElementType(openingElement);
7437
7463
  if (isNodeOfType(openingElement.name, "JSXMemberExpression")) {
7438
7464
  let cursor = openingElement.name;
7439
7465
  while (isNodeOfType(cursor, "JSXMemberExpression")) cursor = cursor.property;
@@ -7685,7 +7711,7 @@ const dialogHasAccessibleName = defineRule({
7685
7711
  if (isTestlikeFilename(context.filename)) return {};
7686
7712
  return { JSXOpeningElement(node) {
7687
7713
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
7688
- const tagName = node.name.name;
7714
+ const tagName = resolveJsxElementType(node);
7689
7715
  if (tagName[0] !== tagName[0]?.toLowerCase()) return;
7690
7716
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
7691
7717
  const roleValue = roleAttribute ? getJsxPropStringValue(roleAttribute) : null;
@@ -11917,7 +11943,7 @@ const forbidDomProps = defineRule({
11917
11943
  return { JSXOpeningElement(node) {
11918
11944
  if (forbidMap.size === 0) return;
11919
11945
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
11920
- const elementName = node.name.name;
11946
+ const elementName = resolveJsxElementType(node);
11921
11947
  if (isReactComponentName(elementName)) return;
11922
11948
  for (const attribute of node.attributes) {
11923
11949
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
@@ -11995,7 +12021,7 @@ const forbidElements = defineRule({
11995
12021
  return {
11996
12022
  JSXOpeningElement(node) {
11997
12023
  if (forbidMap.size === 0) return;
11998
- const fullName = flattenJsxName$1(node.name);
12024
+ const fullName = resolveJsxElementType(node);
11999
12025
  if (!fullName || !forbidMap.has(fullName)) return;
12000
12026
  context.report({
12001
12027
  node: node.name,
@@ -12357,8 +12383,7 @@ const buildMessage$22 = (childTagName) => `Your users get reshuffled HTML becaus
12357
12383
  const isParagraphElement = (candidate) => {
12358
12384
  if (!isNodeOfType(candidate, "JSXElement")) return false;
12359
12385
  const opening = candidate.openingElement;
12360
- if (!isNodeOfType(opening.name, "JSXIdentifier")) return false;
12361
- return opening.name.name === "p";
12386
+ return resolveJsxElementType(opening) === "p";
12362
12387
  };
12363
12388
  const findEnclosingParagraph = (openingElement) => {
12364
12389
  const owningElement = openingElement.parent;
@@ -12379,8 +12404,7 @@ const htmlNoInvalidParagraphChild = defineRule({
12379
12404
  severity: "warn",
12380
12405
  recommendation: "Swap the `<p>` for a `<div>`, or move the child outside the paragraph.",
12381
12406
  create: (context) => ({ JSXOpeningElement(node) {
12382
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
12383
- const childTagName = node.name.name;
12407
+ const childTagName = resolveJsxElementType(node);
12384
12408
  if (!BLOCK_LEVEL_ELEMENTS.has(childTagName)) return;
12385
12409
  if (!findEnclosingParagraph(node)) return;
12386
12410
  context.report({
@@ -12411,7 +12435,7 @@ const getHostTagName = (jsxElement) => {
12411
12435
  if (!isNodeOfType(jsxElement, "JSXElement")) return null;
12412
12436
  const opening = jsxElement.openingElement;
12413
12437
  if (!isNodeOfType(opening.name, "JSXIdentifier")) return null;
12414
- const tagName = opening.name.name;
12438
+ const tagName = resolveJsxElementType(opening);
12415
12439
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return null;
12416
12440
  return tagName;
12417
12441
  };
@@ -12441,7 +12465,7 @@ const findClosestHostAncestor = (jsxElement) => {
12441
12465
  if (isNodeOfType(ancestor, "JSXElement")) {
12442
12466
  const opening = ancestor.openingElement;
12443
12467
  if (isNodeOfType(opening.name, "JSXIdentifier")) {
12444
- const ancestorTag = opening.name.name;
12468
+ const ancestorTag = resolveJsxElementType(opening);
12445
12469
  if (ancestorTag.length === 0) {
12446
12470
  previous = ancestor;
12447
12471
  ancestor = ancestor.parent ?? null;
@@ -12523,8 +12547,7 @@ const buildMessage$20 = (tagName) => `Your users get broken clicks, focus & scre
12523
12547
  const isJsxElementWithTagName = (candidate, tagName) => {
12524
12548
  if (!isNodeOfType(candidate, "JSXElement")) return false;
12525
12549
  const opening = candidate.openingElement;
12526
- if (!isNodeOfType(opening.name, "JSXIdentifier")) return false;
12527
- return opening.name.name === tagName;
12550
+ return resolveJsxElementType(opening) === tagName;
12528
12551
  };
12529
12552
  const findEnclosingSameTag = (openingElement, tagName) => {
12530
12553
  const owningElement = openingElement.parent;
@@ -12545,8 +12568,7 @@ const htmlNoNestedInteractive = defineRule({
12545
12568
  severity: "warn",
12546
12569
  recommendation: "Move the inner `<a>` or `<button>` so it's a sibling, or change the outer one to a plain `<div>` or `<span>`.",
12547
12570
  create: (context) => ({ JSXOpeningElement(node) {
12548
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
12549
- const tagName = node.name.name;
12571
+ const tagName = resolveJsxElementType(node);
12550
12572
  if (tagName !== "a" && tagName !== "button") return;
12551
12573
  if (!findEnclosingSameTag(node, tagName)) return;
12552
12574
  context.report({
@@ -12661,7 +12683,7 @@ const iframeMissingSandbox = defineRule({
12661
12683
  matchByOccurrence: true,
12662
12684
  create: skipNonProductionFiles((context) => ({
12663
12685
  JSXOpeningElement(node) {
12664
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "iframe") return;
12686
+ if (resolveJsxElementType(node) !== "iframe") return;
12665
12687
  const sandboxAttr = hasJsxPropIgnoreCase(node.attributes, "sandbox");
12666
12688
  if (!sandboxAttr) {
12667
12689
  const hasExplicitSrc = Boolean(hasJsxPropIgnoreCase(node.attributes, "src"));
@@ -14589,6 +14611,18 @@ const jsHoistRegexp = defineRule({
14589
14611
  }, { treatIteratorCallbacksAsLoops: true })
14590
14612
  });
14591
14613
  //#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
14592
14626
  //#region src/plugin/rules/js-performance/js-index-maps.ts
14593
14627
  const referencesParameter = (expression, parameterName) => {
14594
14628
  if (!expression) return false;
@@ -14599,7 +14633,7 @@ const referencesParameter = (expression, parameterName) => {
14599
14633
  const isSingleFieldEqualityPredicate = (node) => {
14600
14634
  const callback = node.arguments?.[0];
14601
14635
  if (!isInlineFunctionExpression(callback)) return false;
14602
- const firstParameter = callback.params?.[0];
14636
+ const firstParameter = resolveFirstArgumentBinding(callback.params?.[0]);
14603
14637
  if (!firstParameter || !isNodeOfType(firstParameter, "Identifier")) return false;
14604
14638
  let predicate = null;
14605
14639
  const body = callback.body;
@@ -15938,14 +15972,21 @@ const jsxFilenameExtension = defineRule({
15938
15972
  });
15939
15973
  //#endregion
15940
15974
  //#region src/plugin/utils/is-jsx-fragment-element.ts
15941
- const isJsxFragmentElement = (node) => {
15975
+ const isJsxFragmentElement = (node, scopes) => {
15942
15976
  if (!isNodeOfType(node, "JSXOpeningElement")) return false;
15943
15977
  const elementName = node.name;
15944
- if (isNodeOfType(elementName, "JSXIdentifier")) return elementName.name === "Fragment";
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
+ }
15945
15984
  if (isNodeOfType(elementName, "JSXMemberExpression")) {
15946
15985
  if (!isNodeOfType(elementName.object, "JSXIdentifier")) return false;
15947
- if (elementName.object.name !== "React") return false;
15948
- return elementName.property.name === "Fragment";
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);
15949
15990
  }
15950
15991
  return false;
15951
15992
  };
@@ -15971,7 +16012,7 @@ const jsxFragments = defineRule({
15971
16012
  if (mode !== "syntax") return;
15972
16013
  if (!node.closingElement) return;
15973
16014
  const openingElement = node.openingElement;
15974
- if (!isJsxFragmentElement(openingElement)) return;
16015
+ if (!isJsxFragmentElement(openingElement, context.scopes)) return;
15975
16016
  if (openingElement.attributes.length > 0) return;
15976
16017
  context.report({
15977
16018
  node: openingElement,
@@ -18652,10 +18693,6 @@ const resolveSettings$29 = (settings) => {
18652
18693
  if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
18653
18694
  return reactDoctor.jsxNoScriptUrl ?? {};
18654
18695
  };
18655
- const getElementName = (node) => {
18656
- if (isNodeOfType(node.name, "JSXIdentifier")) return node.name.name;
18657
- return null;
18658
- };
18659
18696
  const isLinkPropForElement = (elementName, attributeName, options) => {
18660
18697
  if (elementName === "a" && attributeName === "href") return true;
18661
18698
  const explicit = options.components?.[elementName];
@@ -18675,7 +18712,8 @@ const jsxNoScriptUrl = defineRule({
18675
18712
  create: (context) => {
18676
18713
  const options = resolveSettings$29(context.settings);
18677
18714
  return { JSXOpeningElement(node) {
18678
- const elementName = getElementName(node);
18715
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
18716
+ const elementName = resolveJsxElementType(node);
18679
18717
  if (!elementName) return;
18680
18718
  for (const attribute of node.attributes) {
18681
18719
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
@@ -18862,11 +18900,6 @@ const checkTarget = (attributeValue) => {
18862
18900
  alternate: false
18863
18901
  };
18864
18902
  };
18865
- const getOpeningElementName = (node) => {
18866
- const name = node.name;
18867
- if (isNodeOfType(name, "JSXIdentifier")) return name.name;
18868
- return null;
18869
- };
18870
18903
  const jsxNoTargetBlank = defineRule({
18871
18904
  id: "jsx-no-target-blank",
18872
18905
  title: "Unsafe target=_blank link",
@@ -18886,7 +18919,8 @@ const jsxNoTargetBlank = defineRule({
18886
18919
  return settings.formComponents.has(tagName);
18887
18920
  };
18888
18921
  return { JSXOpeningElement(node) {
18889
- const tagName = getOpeningElementName(node);
18922
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
18923
+ const tagName = resolveJsxElementType(node);
18890
18924
  if (!tagName) return;
18891
18925
  if (!isLink(tagName) && !isForm(tagName)) return;
18892
18926
  const linkAttributeNames = settings.linkComponents.get(tagName) ?? ["href"];
@@ -19119,7 +19153,7 @@ const jsxNoUselessFragment = defineRule({
19119
19153
  return {
19120
19154
  JSXElement(node) {
19121
19155
  const openingElement = node.openingElement;
19122
- if (!isJsxFragmentElement(openingElement)) return;
19156
+ if (!isJsxFragmentElement(openingElement, context.scopes)) return;
19123
19157
  if (hasJsxKeyAttribute(openingElement)) return;
19124
19158
  if (checkChildren(node, openingElement, node.children)) return;
19125
19159
  if (isChildOfHtmlElement(node)) context.report({
@@ -20361,9 +20395,6 @@ const hasDirective = (programNode, directive) => {
20361
20395
  return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
20362
20396
  };
20363
20397
  //#endregion
20364
- //#region src/plugin/utils/is-uppercase-name.ts
20365
- const isUppercaseName = (name) => UPPERCASE_PATTERN.test(name);
20366
- //#endregion
20367
20398
  //#region src/plugin/utils/is-component-assignment.ts
20368
20399
  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"));
20369
20400
  //#endregion
@@ -20696,7 +20727,7 @@ const nextjsNoAElement = defineRule({
20696
20727
  severity: "warn",
20697
20728
  recommendation: "`import Link from 'next/link'` for client-side navigation, prefetching, and preserved scroll position",
20698
20729
  create: (context) => ({ JSXOpeningElement(node) {
20699
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
20730
+ if (resolveJsxElementType(node) !== "a") return;
20700
20731
  const attributes = node.attributes ?? [];
20701
20732
  const downloadAttribute = findJsxAttribute(attributes, "download");
20702
20733
  if (downloadAttribute) {
@@ -20892,7 +20923,7 @@ const nextjsNoCssLink = defineRule({
20892
20923
  severity: "warn",
20893
20924
  recommendation: "Import CSS directly or use CSS Modules so Next.js can bundle, order, and optimize the stylesheet.",
20894
20925
  create: (context) => ({ JSXOpeningElement(node) {
20895
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "link") return;
20926
+ if (resolveJsxElementType(node) !== "link") return;
20896
20927
  const attributes = node.attributes ?? [];
20897
20928
  const relAttribute = findJsxAttribute(attributes, "rel");
20898
20929
  if (!relAttribute?.value) return;
@@ -21000,7 +21031,7 @@ const nextjsNoFontLink = defineRule({
21000
21031
  severity: "warn",
21001
21032
  recommendation: "`import { Inter } from \"next/font/google\"` for self-hosting, zero layout shift, and no render-blocking requests",
21002
21033
  create: (context) => ({ JSXOpeningElement(node) {
21003
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "link") return;
21034
+ if (resolveJsxElementType(node) !== "link") return;
21004
21035
  const attributes = node.attributes ?? [];
21005
21036
  const hrefAttribute = findJsxAttribute(attributes, "href");
21006
21037
  if (!hrefAttribute?.value) return;
@@ -21175,7 +21206,7 @@ const nextjsNoImgElement = defineRule({
21175
21206
  create: (context) => {
21176
21207
  if (isGeneratedImageRenderContext(context)) return {};
21177
21208
  return { JSXOpeningElement(node) {
21178
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "img") return;
21209
+ if (resolveJsxElementType(node) !== "img") return;
21179
21210
  if (isGeneratedImageRenderContext(context, node)) return;
21180
21211
  const programRoot = findProgramRoot(node);
21181
21212
  if (programRoot && hasEmailTemplateImport(programRoot)) return;
@@ -21222,8 +21253,8 @@ const nextjsNoPolyfillScript = defineRule({
21222
21253
  severity: "warn",
21223
21254
  recommendation: "Next.js includes polyfills for fetch, Promise, Object.assign, Array.from, and 50+ others automatically",
21224
21255
  create: (context) => ({ JSXOpeningElement(node) {
21225
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
21226
- if (node.name.name !== "script" && node.name.name !== "Script") return;
21256
+ const elementName = resolveJsxElementType(node);
21257
+ if (elementName !== "script" && elementName !== "Script") return;
21227
21258
  const srcAttribute = findJsxAttribute(node.attributes ?? [], "src");
21228
21259
  if (!srcAttribute?.value) return;
21229
21260
  const srcValue = isNodeOfType(srcAttribute.value, "Literal") ? srcAttribute.value.value : null;
@@ -23031,10 +23062,15 @@ const isProp = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
23031
23062
  if (!declaringNode) return false;
23032
23063
  return isReactFunctionalComponent(declaringNode) && !isReactFunctionalHOC(analysis, declaringNode) || isCustomHook(declaringNode);
23033
23064
  }));
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
+ };
23034
23071
  const isWholePropsObjectReference = (analysis, ref) => isProp(analysis, ref) && Boolean(ref.resolved?.defs.some((def) => {
23035
23072
  if (def.type !== "Parameter") return false;
23036
- const bindingParent = def.name.parent;
23037
- return isFunctionLike$1(bindingParent);
23073
+ return isWholePropsParameterBinding(def.name);
23038
23074
  }));
23039
23075
  const isIdentifierOrMemberExpression = (node) => isNodeOfType(node, "Identifier") || isNodeOfType(node, "MemberExpression");
23040
23076
  const isPropAlias = (analysis, ref) => {
@@ -26031,6 +26067,56 @@ const noBarrelImport = defineRule({
26031
26067
  }
26032
26068
  });
26033
26069
  //#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
26034
26120
  //#region src/plugin/utils/function-contains-react-render-output.ts
26035
26121
  const NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES = new Set([
26036
26122
  "FunctionDeclaration",
@@ -26050,12 +26136,13 @@ const isCallArgumentFunctionExpression = (node) => {
26050
26136
  return parent.arguments.some((argumentNode) => argumentNode === node);
26051
26137
  };
26052
26138
  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);
26053
26140
  const containsRenderOutput$1 = (rootNode, scopes) => {
26054
26141
  let hasRenderOutput = false;
26055
26142
  walkAst(rootNode, (node) => {
26056
26143
  if (hasRenderOutput) return false;
26057
26144
  if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
26058
- if (node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS)) {
26145
+ if (isRenderOutputExpression(node, scopes)) {
26059
26146
  hasRenderOutput = true;
26060
26147
  return false;
26061
26148
  }
@@ -26066,7 +26153,7 @@ const renderOutputCache = /* @__PURE__ */ new WeakMap();
26066
26153
  const functionContainsReactRenderOutput = (functionNode, scopes) => {
26067
26154
  const cachedEntry = renderOutputCache.get(functionNode);
26068
26155
  if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
26069
- const hasRenderOutput = containsRenderOutput$1(functionNode, scopes);
26156
+ const hasRenderOutput = containsRenderOutput$1(functionNode, scopes) || functionReturnsMatchingExpression(functionNode, scopes, (expression) => isRenderOutputExpression(expression, scopes));
26070
26157
  renderOutputCache.set(functionNode, {
26071
26158
  scopes,
26072
26159
  hasRenderOutput
@@ -28531,7 +28618,7 @@ const noDisabledZoom = defineRule({
28531
28618
  category: "Accessibility",
28532
28619
  recommendation: "Remove `user-scalable=no` and `maximum-scale` from the viewport meta tag. If the layout breaks at 200% zoom, fix the layout instead.",
28533
28620
  create: (context) => ({ JSXOpeningElement(node) {
28534
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "meta") return;
28621
+ if (resolveJsxElementType(node) !== "meta") return;
28535
28622
  const nameAttr = findJsxAttribute(node.attributes ?? [], "name");
28536
28623
  if (!nameAttr?.value) return;
28537
28624
  if ((isNodeOfType(nameAttr.value, "Literal") ? nameAttr.value.value : null) !== "viewport") return;
@@ -31703,7 +31790,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
31703
31790
  severity: "warn",
31704
31791
  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.",
31705
31792
  create: (context) => ({ JSXOpeningElement(node) {
31706
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "img") return;
31793
+ if (resolveJsxElementType(node) !== "img") return;
31707
31794
  const loadingAttribute = hasJsxPropIgnoreCase(node.attributes, "loading");
31708
31795
  if (!loadingAttribute || getJsxPropStringValue(loadingAttribute)?.toLowerCase() !== "lazy") return;
31709
31796
  const fetchPriorityAttribute = hasJsxPropIgnoreCase(node.attributes, "fetchPriority");
@@ -31944,7 +32031,7 @@ const noIndeterminateAttribute = defineRule({
31944
32031
  if (classifyReactNativeFileTarget(context) === "react-native") return {};
31945
32032
  return {
31946
32033
  JSXOpeningElement(node) {
31947
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "input") return;
32034
+ if (resolveJsxElementType(node) !== "input") return;
31948
32035
  let typeAttribute = null;
31949
32036
  let typeAttributeIndex = null;
31950
32037
  let indeterminateAttribute = null;
@@ -33016,11 +33103,13 @@ const noManyBooleanProps = defineRule({
33016
33103
  };
33017
33104
  const checkComponent = (functionNode, param, body, componentName, reportNode) => {
33018
33105
  if (!param) return;
33106
+ const propsBinding = resolveFirstArgumentBinding(param);
33107
+ if (!propsBinding) return;
33019
33108
  if (!functionContainsReactRenderOutput(functionNode, context.scopes)) return;
33020
- if (isNodeOfType(param, "ObjectPattern")) {
33109
+ if (isNodeOfType(propsBinding, "ObjectPattern")) {
33021
33110
  const callbackUsedNames = collectCallbackUsedNames(body, param, context.scopes);
33022
33111
  const booleanLikePropNames = [];
33023
- for (const property of param.properties ?? []) {
33112
+ for (const property of propsBinding.properties ?? []) {
33024
33113
  if (!isNodeOfType(property, "Property")) continue;
33025
33114
  const keyName = isNodeOfType(property.key, "Identifier") ? property.key.name : null;
33026
33115
  if (!keyName) continue;
@@ -33031,7 +33120,7 @@ const noManyBooleanProps = defineRule({
33031
33120
  reportIfMany(booleanLikePropNames, componentName, reportNode);
33032
33121
  return;
33033
33122
  }
33034
- if (isNodeOfType(param, "Identifier")) reportIfMany([...collectBooleanLikePropsFromBody(body, param.name)], componentName, reportNode);
33123
+ if (isNodeOfType(propsBinding, "Identifier")) reportIfMany([...collectBooleanLikePropsFromBody(body, propsBinding.name)], componentName, reportNode);
33035
33124
  };
33036
33125
  return {
33037
33126
  FunctionDeclaration(node) {
@@ -35783,7 +35872,7 @@ const noPreventDefault = defineRule({
35783
35872
  const isServerActionsFramework = hasCapability(context.settings, "server-actions");
35784
35873
  const formMessage = isServerActionsFramework ? FORM_MESSAGE_SERVER_CAPABLE : FORM_MESSAGE_GENERIC;
35785
35874
  return { JSXOpeningElement(node) {
35786
- const elementName = isNodeOfType(node.name, "JSXIdentifier") ? node.name.name : null;
35875
+ const elementName = resolveJsxElementType(node);
35787
35876
  if (!elementName) return;
35788
35877
  const targetEventProps = PREVENT_DEFAULT_ELEMENTS.get(elementName);
35789
35878
  if (!targetEventProps) return;
@@ -38247,9 +38336,10 @@ const noStringFalseOnBooleanAttribute = defineRule({
38247
38336
  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.",
38248
38337
  create: (context) => ({ JSXOpeningElement(node) {
38249
38338
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
38250
- const firstCharacter = node.name.name.charCodeAt(0);
38339
+ const elementName = resolveJsxElementType(node);
38340
+ const firstCharacter = elementName.charCodeAt(0);
38251
38341
  if (firstCharacter < 97 || firstCharacter > 122) return;
38252
- if (node.name.name.includes("-")) return;
38342
+ if (elementName.includes("-")) return;
38253
38343
  for (const attribute of node.attributes) {
38254
38344
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
38255
38345
  if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
@@ -38637,8 +38727,7 @@ const noUncontrolledInput = defineRule({
38637
38727
  const undefinedInitialStateNames = isNodeOfType(componentBody, "BlockStatement") ? collectUndefinedInitialStateNames(componentBody) : /* @__PURE__ */ new Set();
38638
38728
  walkAst(componentBody, (child) => {
38639
38729
  if (!isNodeOfType(child, "JSXOpeningElement")) return;
38640
- if (!isNodeOfType(child.name, "JSXIdentifier")) return;
38641
- const tagName = child.name.name;
38730
+ const tagName = resolveJsxElementType(child);
38642
38731
  if (!UNCONTROLLED_INPUT_TAGS.has(tagName)) return;
38643
38732
  const attributes = child.attributes ?? [];
38644
38733
  if (hasJsxSpreadAttribute(attributes)) return;
@@ -38699,7 +38788,7 @@ const noUndeferredThirdParty = defineRule({
38699
38788
  severity: "warn",
38700
38789
  recommendation: "Use `next/script` with `strategy=\"lazyOnload\"`, or add the `defer` attribute.",
38701
38790
  create: (context) => ({ JSXOpeningElement(node) {
38702
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "script") return;
38791
+ if (resolveJsxElementType(node) !== "script") return;
38703
38792
  const attributes = node.attributes ?? [];
38704
38793
  const srcAttribute = findJsxAttribute(attributes, "src");
38705
38794
  if (!srcAttribute) return;
@@ -39914,7 +40003,7 @@ const noUnknownProperty = defineRule({
39914
40003
  }
39915
40004
  if (fileIsNonReactJsx) return;
39916
40005
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
39917
- const elementType = node.name.name;
40006
+ const elementType = resolveJsxElementType(node);
39918
40007
  const firstCharacter = elementType.charCodeAt(0);
39919
40008
  if (!(firstCharacter >= 97 && firstCharacter <= 122) || elementType === "fbt" || elementType === "fbs") return;
39920
40009
  let isValidHtmlTag = isKnownDomTag(elementType);
@@ -40092,20 +40181,21 @@ const expressionContainsJsxOrCreateElement = (root) => {
40092
40181
  });
40093
40182
  return found;
40094
40183
  };
40184
+ const functionContainsJsxOrCreateElement = (functionNode, scopes) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement);
40095
40185
  const isReactClassComponent = (classNode) => {
40096
40186
  if (isEs6Component(classNode)) return true;
40097
40187
  return expressionContainsJsxOrCreateElement(classNode);
40098
40188
  };
40099
- const findEnclosingComponent = (node) => {
40189
+ const findEnclosingComponent = (node, scopes) => {
40100
40190
  let walker = node.parent;
40101
40191
  while (walker) {
40102
40192
  if (isFunctionLike$1(walker)) {
40103
40193
  const componentName = inferFunctionLikeName(walker);
40104
- if (componentName && isReactComponentName(componentName) && expressionContainsJsxOrCreateElement(walker)) return {
40194
+ if (componentName && isReactComponentName(componentName) && functionContainsJsxOrCreateElement(walker, scopes)) return {
40105
40195
  component: walker,
40106
40196
  name: componentName
40107
40197
  };
40108
- if (!componentName && expressionContainsJsxOrCreateElement(walker) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
40198
+ if (!componentName && functionContainsJsxOrCreateElement(walker, scopes) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
40109
40199
  component: walker,
40110
40200
  name: null
40111
40201
  };
@@ -40344,7 +40434,7 @@ const noUnstableNestedComponents = defineRule({
40344
40434
  if (renderPropRegex.test(propInfo.propName)) return;
40345
40435
  if (settings.allowAsProps) return;
40346
40436
  }
40347
- const enclosing = findEnclosingComponent(candidateNode);
40437
+ const enclosing = findEnclosingComponent(candidateNode, context.scopes);
40348
40438
  if (!enclosing) return;
40349
40439
  const gatedName = propInfo ? null : requiredInstantiationName;
40350
40440
  queuedReports.push({
@@ -40355,7 +40445,7 @@ const noUnstableNestedComponents = defineRule({
40355
40445
  });
40356
40446
  };
40357
40447
  const checkFunctionLike = (node) => {
40358
- if (!expressionContainsJsxOrCreateElement(node)) return;
40448
+ if (!functionContainsJsxOrCreateElement(node, context.scopes)) return;
40359
40449
  const inferredName = inferFunctionLikeName(node);
40360
40450
  const propInfo = isComponentDeclaredInProp(node);
40361
40451
  const isObjectCallback = isObjectCallbackCandidate(node);
@@ -41615,7 +41705,7 @@ const preactPreferOndblclick = defineRule({
41615
41705
  recommendation: "Rename `onDoubleClick` to `onDblClick` because Preact core listens for the DOM `dblclick` event name and `onDoubleClick` never fires.",
41616
41706
  create: (context) => ({ JSXOpeningElement(node) {
41617
41707
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
41618
- const tagName = node.name.name;
41708
+ const tagName = resolveJsxElementType(node);
41619
41709
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return;
41620
41710
  const onDoubleClickAttribute = findJsxAttribute(node.attributes, "onDoubleClick");
41621
41711
  if (!onDoubleClickAttribute) return;
@@ -41635,7 +41725,7 @@ const COMPAT_EXEMPT_INPUT_TYPES = new Set([
41635
41725
  ]);
41636
41726
  const isTextLikeInput = (openingElement) => {
41637
41727
  if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
41638
- const tagName = openingElement.name.name;
41728
+ const tagName = resolveJsxElementType(openingElement);
41639
41729
  if (tagName === "textarea") return true;
41640
41730
  if (tagName !== "input") return false;
41641
41731
  const typeAttribute = findJsxAttribute(openingElement.attributes, "type");
@@ -41792,8 +41882,9 @@ const CROSS_CUTTING_STATE_BOOLEAN_NAMES = new Set([
41792
41882
  ]);
41793
41883
  const collectBooleanPropBindings = (param) => {
41794
41884
  const bindings = /* @__PURE__ */ new Set();
41795
- if (!param || !isNodeOfType(param, "ObjectPattern")) return bindings;
41796
- for (const property of param.properties ?? []) {
41885
+ const propsBinding = resolveFirstArgumentBinding(param);
41886
+ if (!isNodeOfType(propsBinding, "ObjectPattern")) return bindings;
41887
+ for (const property of propsBinding.properties ?? []) {
41797
41888
  if (!isNodeOfType(property, "Property")) continue;
41798
41889
  if (property.computed) continue;
41799
41890
  if (!isNodeOfType(property.key, "Identifier")) continue;
@@ -42054,7 +42145,7 @@ const preferHtmlDialog = defineRule({
42054
42145
  },
42055
42146
  JSXOpeningElement(node) {
42056
42147
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
42057
- const tagName = node.name.name;
42148
+ const tagName = resolveJsxElementType(node);
42058
42149
  if (tagName === "dialog") return;
42059
42150
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return;
42060
42151
  if (!HTML_TAGS.has(tagName)) return;
@@ -44091,7 +44182,7 @@ const renderingAnimateSvgWrapper = defineRule({
44091
44182
  severity: "warn",
44092
44183
  recommendation: "Wrap the SVG in a motion element so animation props apply to a stable wrapper instead of the SVG node itself.",
44093
44184
  create: (context) => ({ JSXOpeningElement(node) {
44094
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "svg") return;
44185
+ if (resolveJsxElementType(node) !== "svg") return;
44095
44186
  if (node.attributes?.some((attribute) => isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && MOTION_ANIMATE_PROPS.has(attribute.name.name))) context.report({
44096
44187
  node,
44097
44188
  message: "This is slow to render because you animate <svg> directly, so wrap it in a <div> or <motion.div> & animate that instead"
@@ -45384,14 +45475,10 @@ const rerenderLazyStateInit = defineRule({
45384
45475
  //#endregion
45385
45476
  //#region src/plugin/rules/performance/rerender-memo-before-early-return.ts
45386
45477
  const isJsxExpression = (node) => Boolean(node && isJsxElementOrFragment(stripParenExpression(node)));
45387
- const callbackReturnsJsx = (callback) => {
45478
+ const callbackReturnsJsx = (callback, scopes) => {
45388
45479
  if (!callback) return false;
45389
45480
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
45390
- const body = callback.body;
45391
- if (isJsxExpression(body)) return true;
45392
- if (!isNodeOfType(body, "BlockStatement")) return false;
45393
- for (const stmt of body.body ?? []) if (isNodeOfType(stmt, "ReturnStatement") && isJsxExpression(stmt.argument)) return true;
45394
- return false;
45481
+ return functionReturnsMatchingExpression(callback, scopes, isJsxExpression);
45395
45482
  };
45396
45483
  const returnArgumentUsesAnyName = (returnStatement, names) => {
45397
45484
  if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
@@ -45469,7 +45556,7 @@ const rerenderMemoBeforeEarlyReturn = defineRule({
45469
45556
  if (!isNodeOfType(stmt, "VariableDeclaration")) continue;
45470
45557
  for (const declarator of stmt.declarations ?? []) {
45471
45558
  const init = declarator.init;
45472
- if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0])) {
45559
+ if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0], context.scopes)) {
45473
45560
  memoNode = declarator;
45474
45561
  callbackGuardTests = collectLeadingCallbackGuardTests(init.arguments?.[0]);
45475
45562
  if (isNodeOfType(declarator.id, "Identifier")) memoConsumerNames.add(declarator.id.name);
@@ -45535,8 +45622,11 @@ const collectFromObjectPattern = (pattern, bindings) => {
45535
45622
  const collectDefaultedEmptyBindings = (functionNode) => {
45536
45623
  const bindings = /* @__PURE__ */ new Map();
45537
45624
  const params = functionNode.params ?? [];
45538
- for (const param of params) collectFromObjectPattern(param, bindings);
45539
- const propsParam = params[0];
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]);
45540
45630
  const body = functionNode.body;
45541
45631
  if (!propsParam || !isNodeOfType(propsParam, "Identifier") || !body) return bindings;
45542
45632
  if (!isNodeOfType(body, "BlockStatement")) return bindings;
@@ -53650,10 +53740,6 @@ const isInvalidStyleExpression = (expression) => {
53650
53740
  }
53651
53741
  return false;
53652
53742
  };
53653
- const getJsxOpeningElementName = (node) => {
53654
- if (isNodeOfType(node.name, "JSXIdentifier")) return node.name.name;
53655
- return null;
53656
- };
53657
53743
  const stylePropObject = defineRule({
53658
53744
  id: "style-prop-object",
53659
53745
  title: "Style prop is not an object",
@@ -53665,7 +53751,8 @@ const stylePropObject = defineRule({
53665
53751
  const allowSet = new Set(allow);
53666
53752
  return {
53667
53753
  JSXOpeningElement(node) {
53668
- const elementName = getJsxOpeningElementName(node);
53754
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
53755
+ const elementName = resolveJsxElementType(node);
53669
53756
  if (elementName && allowSet.has(elementName)) return;
53670
53757
  if (elementName) {
53671
53758
  const firstCharCode = elementName.charCodeAt(0);
@@ -54370,7 +54457,7 @@ const tanstackStartNoAnchorElement = defineRule({
54370
54457
  create: (context) => {
54371
54458
  if (!isInProjectDirectory(context, "routes")) return {};
54372
54459
  return { JSXOpeningElement(node) {
54373
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
54460
+ if (resolveJsxElementType(node) !== "a") return;
54374
54461
  const attributes = node.attributes ?? [];
54375
54462
  const hrefAttribute = findJsxAttribute(attributes, "href");
54376
54463
  if (!hrefAttribute?.value) return;
@@ -54991,8 +55078,7 @@ const voidDomElementsNoChildren = defineRule({
54991
55078
  create: (context) => ({
54992
55079
  JSXElement(node) {
54993
55080
  const openingElement = node.openingElement;
54994
- if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return;
54995
- const tagName = openingElement.name.name;
55081
+ const tagName = resolveJsxElementType(openingElement);
54996
55082
  if (!VOID_DOM_ELEMENTS.has(tagName)) return;
54997
55083
  const hasChildrenContent = node.children.some(isMeaningfulJsxChild);
54998
55084
  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.64950ba",
3
+ "version": "0.7.6-dev.778f1a2",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",