eslint-plugin-react-dom 2.0.0-next.168 → 2.0.0-next.169

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 +91 -105
  2. package/package.json +7 -7
package/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { DEFAULT_ESLINT_REACT_SETTINGS, getConfigAdapters, getDocsUrl, getSettingsFromContext } from "@eslint-react/shared";
2
2
  import * as ER from "@eslint-react/core";
3
3
  import { ESLintUtils } from "@typescript-eslint/utils";
4
- import { unit } from "@eslint-react/eff";
5
4
  import { AST_NODE_TYPES } from "@typescript-eslint/types";
6
5
  import { compare } from "compare-versions";
7
6
  import { RegExp, Reporter } from "@eslint-react/kit";
@@ -47,28 +46,38 @@ const settings = { "react-x": DEFAULT_ESLINT_REACT_SETTINGS };
47
46
  //#endregion
48
47
  //#region package.json
49
48
  var name = "eslint-plugin-react-dom";
50
- var version = "2.0.0-next.168";
49
+ var version = "2.0.0-next.169";
51
50
 
52
51
  //#endregion
53
52
  //#region src/utils/create-jsx-element-resolver.ts
53
+ /**
54
+ * Creates a resolver for JSX elements that determines both the JSX element type
55
+ * and the underlying DOM element type.
56
+ *
57
+ * This resolver handles:
58
+ * 1. Regular HTML elements (div, span, etc.)
59
+ * 2. Polymorphic components (components that can render as different elements via a prop)
60
+ *
61
+ * @param context - The ESLint rule context
62
+ * @returns An object with a resolve method to determine element types
63
+ */
54
64
  function createJsxElementResolver(context) {
55
- const { components, polymorphicPropName } = getSettingsFromContext(context);
65
+ const { polymorphicPropName } = getSettingsFromContext(context);
56
66
  return { resolve(node) {
57
- const name$2 = ER.getElementType(context, node);
58
- const component = components.findLast((c) => c.name === name$2 || c.re.test(name$2));
67
+ const elementName = ER.getElementType(context, node);
59
68
  const result = {
60
- attributes: component?.attributes ?? [],
61
- domElementType: component?.as ?? name$2,
62
- jsxElementType: name$2
69
+ domElementType: elementName,
70
+ jsxElementType: elementName
63
71
  };
64
- if (name$2 === name$2.toLowerCase() || component != null || polymorphicPropName == null) return result;
65
- const initialScope = context.sourceCode.getScope(node);
66
- const polymorphicPropAttr = ER.getAttribute(context, polymorphicPropName, node.openingElement.attributes, initialScope);
67
- if (polymorphicPropAttr != null) {
68
- const polymorphicPropValue = ER.getAttributeValue(context, polymorphicPropAttr, polymorphicPropName);
69
- if (polymorphicPropValue.kind === "some" && typeof polymorphicPropValue.value === "string") return {
72
+ if (elementName === elementName.toLowerCase() || polymorphicPropName == null) return result;
73
+ const getAttribute = ER.getAttribute(context, node.openingElement.attributes, context.sourceCode.getScope(node));
74
+ const polymorphicProp = getAttribute(polymorphicPropName);
75
+ if (polymorphicProp != null) {
76
+ const polymorphicPropValue = ER.resolveAttributeValue(context, polymorphicProp);
77
+ const staticValue = polymorphicPropValue.toStatic(polymorphicPropName);
78
+ if (typeof staticValue === "string") return {
70
79
  ...result,
71
- domElementType: polymorphicPropValue.value
80
+ domElementType: staticValue
72
81
  };
73
82
  }
74
83
  return result;
@@ -79,46 +88,6 @@ function createJsxElementResolver(context) {
79
88
  //#region src/utils/create-rule.ts
80
89
  const createRule = ESLintUtils.RuleCreator(getDocsUrl("dom"));
81
90
 
82
- //#endregion
83
- //#region src/utils/find-custom-component.ts
84
- /**
85
- * Finds a custom component prop by its "as" name.
86
- *
87
- * @param name - The name to match against the prop's "as" property
88
- * @param props - Array of normalized custom component props to search through
89
- * @returns The matching prop if found, undefined otherwise
90
- */
91
- function findCustomComponentProp(name$2, props) {
92
- return props.findLast((a) => a.as === name$2);
93
- }
94
-
95
- //#endregion
96
- //#region src/utils/resolve-attribute.ts
97
- function resolveAttribute(context, attributes, elementNode, attributeName) {
98
- const customComponentProp = findCustomComponentProp(attributeName, attributes);
99
- const propNameOnJsx = customComponentProp?.name ?? attributeName;
100
- const attribute = ER.getAttribute(context, propNameOnJsx, elementNode.openingElement.attributes, context.sourceCode.getScope(elementNode));
101
- if (attribute == null) return {
102
- attribute: unit,
103
- attributeName: propNameOnJsx,
104
- attributeValue: unit,
105
- attributeValueString: customComponentProp?.defaultValue
106
- };
107
- const attributeValue = ER.getAttributeValue(context, attribute, propNameOnJsx);
108
- if (attributeValue.kind === "some" && typeof attributeValue.value === "string") return {
109
- attribute,
110
- attributeName: propNameOnJsx,
111
- attributeValue,
112
- attributeValueString: attributeValue.value
113
- };
114
- return {
115
- attribute,
116
- attributeName: propNameOnJsx,
117
- attributeValue: unit,
118
- attributeValueString: customComponentProp?.defaultValue ?? unit
119
- };
120
- }
121
-
122
91
  //#endregion
123
92
  //#region src/rules/no-dangerously-set-innerhtml.ts
124
93
  const RULE_NAME$16 = "no-dangerously-set-innerhtml";
@@ -141,7 +110,8 @@ const dangerouslySetInnerHTML$1 = "dangerouslySetInnerHTML";
141
110
  function create$16(context) {
142
111
  if (!context.sourceCode.text.includes(dangerouslySetInnerHTML$1)) return {};
143
112
  return { JSXElement(node) {
144
- const attribute = ER.getAttribute(context, dangerouslySetInnerHTML$1, node.openingElement.attributes, context.sourceCode.getScope(node));
113
+ const getAttribute = ER.getAttribute(context, node.openingElement.attributes, context.sourceCode.getScope(node));
114
+ const attribute = getAttribute(dangerouslySetInnerHTML$1);
145
115
  if (attribute == null) return;
146
116
  context.report({
147
117
  messageId: "noDangerouslySetInnerhtml",
@@ -381,26 +351,26 @@ var no_missing_button_type_default = createRule({
381
351
  function create$11(context) {
382
352
  const resolver = createJsxElementResolver(context);
383
353
  return { JSXElement(node) {
384
- const { attributes, domElementType } = resolver.resolve(node);
354
+ const { domElementType } = resolver.resolve(node);
385
355
  if (domElementType !== "button") return;
386
- const typeAttribute = resolveAttribute(context, attributes, node, "type");
387
- if (typeAttribute.attributeValueString != null) return;
388
- if (typeAttribute.attribute == null) {
356
+ const getAttribute = ER.getAttribute(context, node.openingElement.attributes, context.sourceCode.getScope(node));
357
+ const typeAttribute = getAttribute("type");
358
+ if (typeAttribute == null) {
389
359
  context.report({
390
360
  messageId: "noMissingButtonType",
391
361
  node: node.openingElement,
392
362
  suggest: getSuggest((type) => (fixer) => {
393
- return fixer.insertTextAfter(node.openingElement.name, ` ${typeAttribute.attributeName}="${type}"`);
363
+ return fixer.insertTextAfter(node.openingElement.name, ` type="${type}"`);
394
364
  })
395
365
  });
396
366
  return;
397
367
  }
368
+ if (typeof ER.resolveAttributeValue(context, typeAttribute).toStatic("type") === "string") return;
398
369
  context.report({
399
370
  messageId: "noMissingButtonType",
400
- node: typeAttribute.attributeValue?.node ?? typeAttribute.attribute,
371
+ node: typeAttribute,
401
372
  suggest: getSuggest((type) => (fixer) => {
402
- if (typeAttribute.attribute == null) return null;
403
- return fixer.replaceText(typeAttribute.attribute, `${typeAttribute.attributeName}="${type}"`);
373
+ return fixer.replaceText(typeAttribute, `type="${type}"`);
404
374
  })
405
375
  });
406
376
  } };
@@ -439,11 +409,11 @@ var no_missing_iframe_sandbox_default = createRule({
439
409
  function create$10(context) {
440
410
  const resolver = createJsxElementResolver(context);
441
411
  return { JSXElement(node) {
442
- const { attributes, domElementType } = resolver.resolve(node);
412
+ const { domElementType } = resolver.resolve(node);
443
413
  if (domElementType !== "iframe") return;
444
- const sandboxAttribute = resolveAttribute(context, attributes, node, "sandbox");
445
- if (sandboxAttribute.attributeValueString != null) return;
446
- if (sandboxAttribute.attribute == null) {
414
+ const getAttribute = ER.getAttribute(context, node.openingElement.attributes, context.sourceCode.getScope(node));
415
+ const sandboxAttribute = getAttribute("sandbox");
416
+ if (sandboxAttribute == null) {
447
417
  context.report({
448
418
  messageId: "noMissingIframeSandbox",
449
419
  node: node.openingElement,
@@ -451,21 +421,23 @@ function create$10(context) {
451
421
  messageId: "addIframeSandbox",
452
422
  data: { value: "" },
453
423
  fix(fixer) {
454
- return fixer.insertTextAfter(node.openingElement.name, ` ${sandboxAttribute.attributeName}=""`);
424
+ return fixer.insertTextAfter(node.openingElement.name, ` sandbox=""`);
455
425
  }
456
426
  }]
457
427
  });
458
428
  return;
459
429
  }
430
+ const sandboxAttributeValue = ER.resolveAttributeValue(context, sandboxAttribute);
431
+ if (typeof sandboxAttributeValue.toStatic("sandbox") === "string") return;
460
432
  context.report({
461
433
  messageId: "noMissingIframeSandbox",
462
- node: sandboxAttribute.attributeValue?.node ?? sandboxAttribute.attribute,
434
+ node: sandboxAttributeValue.node ?? sandboxAttribute,
463
435
  suggest: [{
464
436
  messageId: "addIframeSandbox",
465
437
  data: { value: "" },
466
438
  fix(fixer) {
467
- if (sandboxAttribute.attribute == null) return null;
468
- return fixer.replaceText(sandboxAttribute.attribute, `${sandboxAttribute.attributeName}=""`);
439
+ if (sandboxAttributeValue.kind.startsWith("spread")) return null;
440
+ return fixer.replaceText(sandboxAttribute, `sandbox=""`);
469
441
  }
470
442
  }]
471
443
  });
@@ -637,10 +609,6 @@ function create$7(context) {
637
609
  //#region src/rules/no-script-url.ts
638
610
  const RULE_NAME$6 = "no-script-url";
639
611
  const RULE_FEATURES$5 = [];
640
- /**
641
- * This rule is adapted from eslint-plugin-solid's jsx-no-script-url rule under the MIT license.
642
- * Thank you for your work!
643
- */
644
612
  var no_script_url_default = createRule({
645
613
  meta: {
646
614
  type: "problem",
@@ -658,9 +626,8 @@ var no_script_url_default = createRule({
658
626
  function create$6(context) {
659
627
  return { JSXAttribute(node) {
660
628
  if (node.name.type !== AST_NODE_TYPES.JSXIdentifier || node.value == null) return;
661
- const attributeValue = ER.getAttributeValue(context, node, ER.getAttributeName(context, node));
662
- if (attributeValue.kind === "none" || typeof attributeValue.value !== "string") return;
663
- if (RegExp.JAVASCRIPT_PROTOCOL.test(attributeValue.value)) context.report({
629
+ const value = ER.resolveAttributeValue(context, node).toStatic();
630
+ if (typeof value === "string" && RegExp.JAVASCRIPT_PROTOCOL.test(value)) context.report({
664
631
  messageId: "noScriptUrl",
665
632
  node: node.value
666
633
  });
@@ -686,16 +653,14 @@ var no_string_style_prop_default = createRule({
686
653
  defaultOptions: []
687
654
  });
688
655
  function create$5(context) {
689
- const resolver = createJsxElementResolver(context);
690
656
  return { JSXElement(node) {
691
- const { attributes } = resolver.resolve(node);
692
- const { attribute, attributeName, attributeValue, attributeValueString } = resolveAttribute(context, attributes, node, "style");
693
- if (attributeName !== "style") return;
694
- if (attribute == null || attributeValue?.node == null) return;
695
- if (attributeValueString == null) return;
696
- context.report({
657
+ const getAttribute = ER.getAttribute(context, node.openingElement.attributes, context.sourceCode.getScope(node));
658
+ const attribute = getAttribute("style");
659
+ if (attribute == null) return;
660
+ const attributeValue = ER.resolveAttributeValue(context, attribute);
661
+ if (typeof attributeValue.toStatic() === "string") context.report({
697
662
  messageId: "noStringStyleProp",
698
- node: attributeValue.node
663
+ node: attributeValue.node ?? attribute
699
664
  });
700
665
  } };
701
666
  }
@@ -1866,12 +1831,16 @@ var no_unsafe_iframe_sandbox_default = createRule({
1866
1831
  function create$3(context) {
1867
1832
  const resolver = createJsxElementResolver(context);
1868
1833
  return { JSXElement(node) {
1869
- const { attributes, domElementType } = resolver.resolve(node);
1834
+ const { domElementType } = resolver.resolve(node);
1870
1835
  if (domElementType !== "iframe") return;
1871
- const sandboxAttribute = resolveAttribute(context, attributes, node, "sandbox");
1872
- if (!isSafeSandbox(sandboxAttribute.attributeValueString)) context.report({
1836
+ const getAttribute = ER.getAttribute(context, node.openingElement.attributes, context.sourceCode.getScope(node));
1837
+ const sandboxAttribute = getAttribute("sandbox");
1838
+ if (sandboxAttribute == null) return;
1839
+ const sandboxValue = ER.resolveAttributeValue(context, sandboxAttribute);
1840
+ const sandboxValueStatic = sandboxValue.toStatic("sandbox");
1841
+ if (!isSafeSandbox(sandboxValueStatic)) context.report({
1873
1842
  messageId: "noUnsafeIframeSandbox",
1874
- node: sandboxAttribute.attributeValue?.node ?? sandboxAttribute.attribute ?? node
1843
+ node: sandboxValue.node ?? sandboxAttribute
1875
1844
  });
1876
1845
  } };
1877
1846
  }
@@ -1880,12 +1849,24 @@ function create$3(context) {
1880
1849
  //#region src/rules/no-unsafe-target-blank.ts
1881
1850
  const RULE_NAME$2 = "no-unsafe-target-blank";
1882
1851
  const RULE_FEATURES$2 = ["FIX"];
1852
+ /**
1853
+ * Checks if a value appears to be an external link.
1854
+ * External links typically start with http(s):// or have protocol-relative format.
1855
+ * @param value - The value to check
1856
+ * @returns Whether the value represents an external link
1857
+ */
1883
1858
  function isExternalLinkLike(value) {
1884
- if (value == null) return false;
1859
+ if (typeof value !== "string") return false;
1885
1860
  return value.startsWith("https://") || /^(?:\w+:|\/\/)/u.test(value);
1886
1861
  }
1862
+ /**
1863
+ * Checks if a rel attribute value contains the necessary security attributes.
1864
+ * At minimum, it should contain "noreferrer".
1865
+ * @param value - The rel attribute value to check
1866
+ * @returns Whether the rel value is considered secure
1867
+ */
1887
1868
  function isSafeRel(value) {
1888
- if (value == null) return false;
1869
+ if (typeof value !== "string") return false;
1889
1870
  return value === "noreferrer" || /\bnoreferrer\b/u.test(value);
1890
1871
  }
1891
1872
  var no_unsafe_target_blank_default = createRule({
@@ -1910,35 +1891,40 @@ var no_unsafe_target_blank_default = createRule({
1910
1891
  function create$2(context) {
1911
1892
  const resolver = createJsxElementResolver(context);
1912
1893
  return { JSXElement(node) {
1913
- const { attributes, domElementType } = resolver.resolve(node);
1894
+ const { domElementType } = resolver.resolve(node);
1914
1895
  if (domElementType !== "a") return;
1915
- const targetAttribute = resolveAttribute(context, attributes, node, "target");
1916
- if (targetAttribute.attributeValueString !== "_blank") return;
1917
- const hrefAttribute = resolveAttribute(context, attributes, node, "href");
1918
- if (!isExternalLinkLike(hrefAttribute.attributeValueString)) return;
1919
- const relAttribute = resolveAttribute(context, attributes, node, "rel");
1920
- if (isSafeRel(relAttribute.attributeValueString)) return;
1921
- if (relAttribute.attribute == null) {
1896
+ const getAttributes = ER.getAttribute(context, node.openingElement.attributes, context.sourceCode.getScope(node));
1897
+ const targetAttribute = getAttributes("target");
1898
+ if (targetAttribute == null) return;
1899
+ const targetAttributeValue = ER.resolveAttributeValue(context, targetAttribute).toStatic("target");
1900
+ if (targetAttributeValue !== "_blank") return;
1901
+ const hrefAttribute = getAttributes("href");
1902
+ if (hrefAttribute == null) return;
1903
+ const hrefAttributeValue = ER.resolveAttributeValue(context, hrefAttribute).toStatic("href");
1904
+ if (!isExternalLinkLike(hrefAttributeValue)) return;
1905
+ const relAttribute = getAttributes("rel");
1906
+ if (relAttribute == null) {
1922
1907
  context.report({
1923
1908
  messageId: "noUnsafeTargetBlank",
1924
1909
  node: node.openingElement,
1925
1910
  suggest: [{
1926
1911
  messageId: "addRelNoreferrerNoopener",
1927
1912
  fix(fixer) {
1928
- return fixer.insertTextAfter(node.openingElement.name, ` ${relAttribute.attributeName}="noreferrer noopener"`);
1913
+ return fixer.insertTextAfter(node.openingElement.name, ` rel="noreferrer noopener"`);
1929
1914
  }
1930
1915
  }]
1931
1916
  });
1932
1917
  return;
1933
1918
  }
1919
+ const relAttributeValue = ER.resolveAttributeValue(context, relAttribute).toStatic("rel");
1920
+ if (isSafeRel(relAttributeValue)) return;
1934
1921
  context.report({
1935
1922
  messageId: "noUnsafeTargetBlank",
1936
- node: relAttribute.attributeValue?.node ?? relAttribute.attribute,
1923
+ node: relAttribute,
1937
1924
  suggest: [{
1938
1925
  messageId: "addRelNoreferrerNoopener",
1939
1926
  fix(fixer) {
1940
- if (relAttribute.attribute == null) return null;
1941
- return fixer.replaceText(relAttribute.attribute, `${relAttribute.attributeName}="noreferrer noopener"`);
1927
+ return fixer.replaceText(relAttribute, `rel="noreferrer noopener"`);
1942
1928
  }
1943
1929
  }]
1944
1930
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-dom",
3
- "version": "2.0.0-next.168",
3
+ "version": "2.0.0-next.169",
4
4
  "description": "ESLint React's ESLint plugin for React DOM related rules.",
5
5
  "keywords": [
6
6
  "react",
@@ -41,12 +41,12 @@
41
41
  "compare-versions": "^6.1.1",
42
42
  "string-ts": "^2.2.1",
43
43
  "ts-pattern": "^5.8.0",
44
- "@eslint-react/ast": "2.0.0-next.168",
45
- "@eslint-react/core": "2.0.0-next.168",
46
- "@eslint-react/eff": "2.0.0-next.168",
47
- "@eslint-react/shared": "2.0.0-next.168",
48
- "@eslint-react/kit": "2.0.0-next.168",
49
- "@eslint-react/var": "2.0.0-next.168"
44
+ "@eslint-react/ast": "2.0.0-next.169",
45
+ "@eslint-react/eff": "2.0.0-next.169",
46
+ "@eslint-react/kit": "2.0.0-next.169",
47
+ "@eslint-react/var": "2.0.0-next.169",
48
+ "@eslint-react/shared": "2.0.0-next.169",
49
+ "@eslint-react/core": "2.0.0-next.169"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/react": "^19.1.12",