oxlint-plugin-react-doctor 0.7.6-dev.82e10cd → 0.7.6-dev.8528def

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 +95 -75
  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) {
@@ -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"));
@@ -15291,9 +15313,21 @@ const isSmallFixedListMember = (receiver) => {
15291
15313
  };
15292
15314
  const getResolvedInitializer = (receiver) => {
15293
15315
  if (!isNodeOfType(receiver, "Identifier")) return null;
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;
15316
+ const binding = findVariableInitializer(receiver, receiver.name);
15317
+ const initializer = binding?.initializer ?? null;
15318
+ if (!binding || !initializer) return null;
15319
+ const isDefault = isNodeOfType(binding.bindingIdentifier.parent, "AssignmentPattern");
15320
+ if (isNodeOfType(initializer, "Identifier")) {
15321
+ const aliased = findVariableInitializer(initializer, initializer.name);
15322
+ if (aliased?.initializer) return {
15323
+ initializer: aliased.initializer,
15324
+ isDefault: isDefault || isNodeOfType(aliased.bindingIdentifier.parent, "AssignmentPattern")
15325
+ };
15326
+ }
15327
+ return {
15328
+ initializer,
15329
+ isDefault
15330
+ };
15297
15331
  };
15298
15332
  const isSplitCall = (expression) => {
15299
15333
  if (!expression) return false;
@@ -15459,8 +15493,8 @@ const isBoundedConstantCollection = (collection) => {
15459
15493
  if (isScreamingSnakeCaseConstantReceiver(stripped)) return true;
15460
15494
  if (isSmallInlineLiteralArray(stripped)) return true;
15461
15495
  if (isNodeOfType(stripped, "Identifier")) {
15462
- const initializer = getResolvedInitializer(stripped);
15463
- if (initializer && isSmallInlineLiteralArray(initializer)) return true;
15496
+ const resolved = getResolvedInitializer(stripped);
15497
+ if (resolved && !resolved.isDefault && isSmallInlineLiteralArray(resolved.initializer)) return true;
15464
15498
  }
15465
15499
  return false;
15466
15500
  };
@@ -15512,8 +15546,8 @@ const jsSetMapLookups = defineRule({
15512
15546
  if (isIndexedArrayElementWithStringArgument(receiver, node.arguments?.[0])) return;
15513
15547
  const resolvedInitializer = getResolvedInitializer(receiver);
15514
15548
  if (resolvedInitializer) {
15515
- if (isLikelyStringReceiver(resolvedInitializer)) return;
15516
- if (isSmallInlineLiteralArray(resolvedInitializer)) return;
15549
+ if (isLikelyStringReceiver(resolvedInitializer.initializer)) return;
15550
+ if (!resolvedInitializer.isDefault && isSmallInlineLiteralArray(resolvedInitializer.initializer)) return;
15517
15551
  }
15518
15552
  if (isStringElementOfSplitIteration(receiver)) return;
15519
15553
  if (isReceiverDeclaredInNearestLoop(receiver, node)) return;
@@ -18640,10 +18674,6 @@ const resolveSettings$29 = (settings) => {
18640
18674
  if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
18641
18675
  return reactDoctor.jsxNoScriptUrl ?? {};
18642
18676
  };
18643
- const getElementName = (node) => {
18644
- if (isNodeOfType(node.name, "JSXIdentifier")) return node.name.name;
18645
- return null;
18646
- };
18647
18677
  const isLinkPropForElement = (elementName, attributeName, options) => {
18648
18678
  if (elementName === "a" && attributeName === "href") return true;
18649
18679
  const explicit = options.components?.[elementName];
@@ -18663,7 +18693,8 @@ const jsxNoScriptUrl = defineRule({
18663
18693
  create: (context) => {
18664
18694
  const options = resolveSettings$29(context.settings);
18665
18695
  return { JSXOpeningElement(node) {
18666
- const elementName = getElementName(node);
18696
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
18697
+ const elementName = resolveJsxElementType(node);
18667
18698
  if (!elementName) return;
18668
18699
  for (const attribute of node.attributes) {
18669
18700
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
@@ -18850,11 +18881,6 @@ const checkTarget = (attributeValue) => {
18850
18881
  alternate: false
18851
18882
  };
18852
18883
  };
18853
- const getOpeningElementName = (node) => {
18854
- const name = node.name;
18855
- if (isNodeOfType(name, "JSXIdentifier")) return name.name;
18856
- return null;
18857
- };
18858
18884
  const jsxNoTargetBlank = defineRule({
18859
18885
  id: "jsx-no-target-blank",
18860
18886
  title: "Unsafe target=_blank link",
@@ -18874,7 +18900,8 @@ const jsxNoTargetBlank = defineRule({
18874
18900
  return settings.formComponents.has(tagName);
18875
18901
  };
18876
18902
  return { JSXOpeningElement(node) {
18877
- const tagName = getOpeningElementName(node);
18903
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
18904
+ const tagName = resolveJsxElementType(node);
18878
18905
  if (!tagName) return;
18879
18906
  if (!isLink(tagName) && !isForm(tagName)) return;
18880
18907
  const linkAttributeNames = settings.linkComponents.get(tagName) ?? ["href"];
@@ -20349,9 +20376,6 @@ const hasDirective = (programNode, directive) => {
20349
20376
  return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
20350
20377
  };
20351
20378
  //#endregion
20352
- //#region src/plugin/utils/is-uppercase-name.ts
20353
- const isUppercaseName = (name) => UPPERCASE_PATTERN.test(name);
20354
- //#endregion
20355
20379
  //#region src/plugin/utils/is-component-assignment.ts
20356
20380
  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"));
20357
20381
  //#endregion
@@ -20684,7 +20708,7 @@ const nextjsNoAElement = defineRule({
20684
20708
  severity: "warn",
20685
20709
  recommendation: "`import Link from 'next/link'` for client-side navigation, prefetching, and preserved scroll position",
20686
20710
  create: (context) => ({ JSXOpeningElement(node) {
20687
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
20711
+ if (resolveJsxElementType(node) !== "a") return;
20688
20712
  const attributes = node.attributes ?? [];
20689
20713
  const downloadAttribute = findJsxAttribute(attributes, "download");
20690
20714
  if (downloadAttribute) {
@@ -20880,7 +20904,7 @@ const nextjsNoCssLink = defineRule({
20880
20904
  severity: "warn",
20881
20905
  recommendation: "Import CSS directly or use CSS Modules so Next.js can bundle, order, and optimize the stylesheet.",
20882
20906
  create: (context) => ({ JSXOpeningElement(node) {
20883
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "link") return;
20907
+ if (resolveJsxElementType(node) !== "link") return;
20884
20908
  const attributes = node.attributes ?? [];
20885
20909
  const relAttribute = findJsxAttribute(attributes, "rel");
20886
20910
  if (!relAttribute?.value) return;
@@ -20988,7 +21012,7 @@ const nextjsNoFontLink = defineRule({
20988
21012
  severity: "warn",
20989
21013
  recommendation: "`import { Inter } from \"next/font/google\"` for self-hosting, zero layout shift, and no render-blocking requests",
20990
21014
  create: (context) => ({ JSXOpeningElement(node) {
20991
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "link") return;
21015
+ if (resolveJsxElementType(node) !== "link") return;
20992
21016
  const attributes = node.attributes ?? [];
20993
21017
  const hrefAttribute = findJsxAttribute(attributes, "href");
20994
21018
  if (!hrefAttribute?.value) return;
@@ -21163,7 +21187,7 @@ const nextjsNoImgElement = defineRule({
21163
21187
  create: (context) => {
21164
21188
  if (isGeneratedImageRenderContext(context)) return {};
21165
21189
  return { JSXOpeningElement(node) {
21166
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "img") return;
21190
+ if (resolveJsxElementType(node) !== "img") return;
21167
21191
  if (isGeneratedImageRenderContext(context, node)) return;
21168
21192
  const programRoot = findProgramRoot(node);
21169
21193
  if (programRoot && hasEmailTemplateImport(programRoot)) return;
@@ -21210,8 +21234,8 @@ const nextjsNoPolyfillScript = defineRule({
21210
21234
  severity: "warn",
21211
21235
  recommendation: "Next.js includes polyfills for fetch, Promise, Object.assign, Array.from, and 50+ others automatically",
21212
21236
  create: (context) => ({ JSXOpeningElement(node) {
21213
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
21214
- if (node.name.name !== "script" && node.name.name !== "Script") return;
21237
+ const elementName = resolveJsxElementType(node);
21238
+ if (elementName !== "script" && elementName !== "Script") return;
21215
21239
  const srcAttribute = findJsxAttribute(node.attributes ?? [], "src");
21216
21240
  if (!srcAttribute?.value) return;
21217
21241
  const srcValue = isNodeOfType(srcAttribute.value, "Literal") ? srcAttribute.value.value : null;
@@ -28519,7 +28543,7 @@ const noDisabledZoom = defineRule({
28519
28543
  category: "Accessibility",
28520
28544
  recommendation: "Remove `user-scalable=no` and `maximum-scale` from the viewport meta tag. If the layout breaks at 200% zoom, fix the layout instead.",
28521
28545
  create: (context) => ({ JSXOpeningElement(node) {
28522
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "meta") return;
28546
+ if (resolveJsxElementType(node) !== "meta") return;
28523
28547
  const nameAttr = findJsxAttribute(node.attributes ?? [], "name");
28524
28548
  if (!nameAttr?.value) return;
28525
28549
  if ((isNodeOfType(nameAttr.value, "Literal") ? nameAttr.value.value : null) !== "viewport") return;
@@ -31691,7 +31715,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
31691
31715
  severity: "warn",
31692
31716
  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.",
31693
31717
  create: (context) => ({ JSXOpeningElement(node) {
31694
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "img") return;
31718
+ if (resolveJsxElementType(node) !== "img") return;
31695
31719
  const loadingAttribute = hasJsxPropIgnoreCase(node.attributes, "loading");
31696
31720
  if (!loadingAttribute || getJsxPropStringValue(loadingAttribute)?.toLowerCase() !== "lazy") return;
31697
31721
  const fetchPriorityAttribute = hasJsxPropIgnoreCase(node.attributes, "fetchPriority");
@@ -31932,7 +31956,7 @@ const noIndeterminateAttribute = defineRule({
31932
31956
  if (classifyReactNativeFileTarget(context) === "react-native") return {};
31933
31957
  return {
31934
31958
  JSXOpeningElement(node) {
31935
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "input") return;
31959
+ if (resolveJsxElementType(node) !== "input") return;
31936
31960
  let typeAttribute = null;
31937
31961
  let typeAttributeIndex = null;
31938
31962
  let indeterminateAttribute = null;
@@ -35771,7 +35795,7 @@ const noPreventDefault = defineRule({
35771
35795
  const isServerActionsFramework = hasCapability(context.settings, "server-actions");
35772
35796
  const formMessage = isServerActionsFramework ? FORM_MESSAGE_SERVER_CAPABLE : FORM_MESSAGE_GENERIC;
35773
35797
  return { JSXOpeningElement(node) {
35774
- const elementName = isNodeOfType(node.name, "JSXIdentifier") ? node.name.name : null;
35798
+ const elementName = resolveJsxElementType(node);
35775
35799
  if (!elementName) return;
35776
35800
  const targetEventProps = PREVENT_DEFAULT_ELEMENTS.get(elementName);
35777
35801
  if (!targetEventProps) return;
@@ -38235,9 +38259,10 @@ const noStringFalseOnBooleanAttribute = defineRule({
38235
38259
  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.",
38236
38260
  create: (context) => ({ JSXOpeningElement(node) {
38237
38261
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
38238
- const firstCharacter = node.name.name.charCodeAt(0);
38262
+ const elementName = resolveJsxElementType(node);
38263
+ const firstCharacter = elementName.charCodeAt(0);
38239
38264
  if (firstCharacter < 97 || firstCharacter > 122) return;
38240
- if (node.name.name.includes("-")) return;
38265
+ if (elementName.includes("-")) return;
38241
38266
  for (const attribute of node.attributes) {
38242
38267
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
38243
38268
  if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
@@ -38625,8 +38650,7 @@ const noUncontrolledInput = defineRule({
38625
38650
  const undefinedInitialStateNames = isNodeOfType(componentBody, "BlockStatement") ? collectUndefinedInitialStateNames(componentBody) : /* @__PURE__ */ new Set();
38626
38651
  walkAst(componentBody, (child) => {
38627
38652
  if (!isNodeOfType(child, "JSXOpeningElement")) return;
38628
- if (!isNodeOfType(child.name, "JSXIdentifier")) return;
38629
- const tagName = child.name.name;
38653
+ const tagName = resolveJsxElementType(child);
38630
38654
  if (!UNCONTROLLED_INPUT_TAGS.has(tagName)) return;
38631
38655
  const attributes = child.attributes ?? [];
38632
38656
  if (hasJsxSpreadAttribute(attributes)) return;
@@ -38687,7 +38711,7 @@ const noUndeferredThirdParty = defineRule({
38687
38711
  severity: "warn",
38688
38712
  recommendation: "Use `next/script` with `strategy=\"lazyOnload\"`, or add the `defer` attribute.",
38689
38713
  create: (context) => ({ JSXOpeningElement(node) {
38690
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "script") return;
38714
+ if (resolveJsxElementType(node) !== "script") return;
38691
38715
  const attributes = node.attributes ?? [];
38692
38716
  const srcAttribute = findJsxAttribute(attributes, "src");
38693
38717
  if (!srcAttribute) return;
@@ -39902,7 +39926,7 @@ const noUnknownProperty = defineRule({
39902
39926
  }
39903
39927
  if (fileIsNonReactJsx) return;
39904
39928
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
39905
- const elementType = node.name.name;
39929
+ const elementType = resolveJsxElementType(node);
39906
39930
  const firstCharacter = elementType.charCodeAt(0);
39907
39931
  if (!(firstCharacter >= 97 && firstCharacter <= 122) || elementType === "fbt" || elementType === "fbs") return;
39908
39932
  let isValidHtmlTag = isKnownDomTag(elementType);
@@ -41603,7 +41627,7 @@ const preactPreferOndblclick = defineRule({
41603
41627
  recommendation: "Rename `onDoubleClick` to `onDblClick` because Preact core listens for the DOM `dblclick` event name and `onDoubleClick` never fires.",
41604
41628
  create: (context) => ({ JSXOpeningElement(node) {
41605
41629
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
41606
- const tagName = node.name.name;
41630
+ const tagName = resolveJsxElementType(node);
41607
41631
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return;
41608
41632
  const onDoubleClickAttribute = findJsxAttribute(node.attributes, "onDoubleClick");
41609
41633
  if (!onDoubleClickAttribute) return;
@@ -41623,7 +41647,7 @@ const COMPAT_EXEMPT_INPUT_TYPES = new Set([
41623
41647
  ]);
41624
41648
  const isTextLikeInput = (openingElement) => {
41625
41649
  if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
41626
- const tagName = openingElement.name.name;
41650
+ const tagName = resolveJsxElementType(openingElement);
41627
41651
  if (tagName === "textarea") return true;
41628
41652
  if (tagName !== "input") return false;
41629
41653
  const typeAttribute = findJsxAttribute(openingElement.attributes, "type");
@@ -42042,7 +42066,7 @@ const preferHtmlDialog = defineRule({
42042
42066
  },
42043
42067
  JSXOpeningElement(node) {
42044
42068
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
42045
- const tagName = node.name.name;
42069
+ const tagName = resolveJsxElementType(node);
42046
42070
  if (tagName === "dialog") return;
42047
42071
  if (tagName.length === 0 || tagName[0] !== tagName[0].toLowerCase()) return;
42048
42072
  if (!HTML_TAGS.has(tagName)) return;
@@ -44079,7 +44103,7 @@ const renderingAnimateSvgWrapper = defineRule({
44079
44103
  severity: "warn",
44080
44104
  recommendation: "Wrap the SVG in a motion element so animation props apply to a stable wrapper instead of the SVG node itself.",
44081
44105
  create: (context) => ({ JSXOpeningElement(node) {
44082
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "svg") return;
44106
+ if (resolveJsxElementType(node) !== "svg") return;
44083
44107
  if (node.attributes?.some((attribute) => isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && MOTION_ANIMATE_PROPS.has(attribute.name.name))) context.report({
44084
44108
  node,
44085
44109
  message: "This is slow to render because you animate <svg> directly, so wrap it in a <div> or <motion.div> & animate that instead"
@@ -53638,10 +53662,6 @@ const isInvalidStyleExpression = (expression) => {
53638
53662
  }
53639
53663
  return false;
53640
53664
  };
53641
- const getJsxOpeningElementName = (node) => {
53642
- if (isNodeOfType(node.name, "JSXIdentifier")) return node.name.name;
53643
- return null;
53644
- };
53645
53665
  const stylePropObject = defineRule({
53646
53666
  id: "style-prop-object",
53647
53667
  title: "Style prop is not an object",
@@ -53653,7 +53673,8 @@ const stylePropObject = defineRule({
53653
53673
  const allowSet = new Set(allow);
53654
53674
  return {
53655
53675
  JSXOpeningElement(node) {
53656
- const elementName = getJsxOpeningElementName(node);
53676
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
53677
+ const elementName = resolveJsxElementType(node);
53657
53678
  if (elementName && allowSet.has(elementName)) return;
53658
53679
  if (elementName) {
53659
53680
  const firstCharCode = elementName.charCodeAt(0);
@@ -54358,7 +54379,7 @@ const tanstackStartNoAnchorElement = defineRule({
54358
54379
  create: (context) => {
54359
54380
  if (!isInProjectDirectory(context, "routes")) return {};
54360
54381
  return { JSXOpeningElement(node) {
54361
- if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a") return;
54382
+ if (resolveJsxElementType(node) !== "a") return;
54362
54383
  const attributes = node.attributes ?? [];
54363
54384
  const hrefAttribute = findJsxAttribute(attributes, "href");
54364
54385
  if (!hrefAttribute?.value) return;
@@ -54979,8 +55000,7 @@ const voidDomElementsNoChildren = defineRule({
54979
55000
  create: (context) => ({
54980
55001
  JSXElement(node) {
54981
55002
  const openingElement = node.openingElement;
54982
- if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return;
54983
- const tagName = openingElement.name.name;
55003
+ const tagName = resolveJsxElementType(openingElement);
54984
55004
  if (!VOID_DOM_ELEMENTS.has(tagName)) return;
54985
55005
  const hasChildrenContent = node.children.some(isMeaningfulJsxChild);
54986
55006
  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.82e10cd",
3
+ "version": "0.7.6-dev.8528def",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",