eslint-plugin-react-dom 2.0.0-next.52 → 2.0.0-next.53

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.
package/dist/index.d.ts CHANGED
@@ -2,6 +2,10 @@ import { CamelCase } from 'string-ts';
2
2
  import * as _typescript_eslint_utils_ts_eslint from '@typescript-eslint/utils/ts-eslint';
3
3
  import { RulePreset } from '@eslint-react/kit';
4
4
 
5
+ declare const RULE_NAME$2 = "no-unsafe-target-blank";
6
+ type MessageID$2 = CamelCase<typeof RULE_NAME$2> | RuleSuggestMessageID$2;
7
+ type RuleSuggestMessageID$2 = "addRelNoreferrerNoopener";
8
+
5
9
  declare const RULE_NAME$1 = "no-missing-iframe-sandbox";
6
10
  type MessageID$1 = CamelCase<typeof RULE_NAME$1> | RuleSuggestMessageID$1;
7
11
  type RuleSuggestMessageID$1 = "addIframeSandbox";
@@ -33,7 +37,7 @@ declare const _default: {
33
37
  readonly "no-script-url": _typescript_eslint_utils_ts_eslint.RuleModule<"noScriptUrl", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
34
38
  readonly "no-unknown-property": _typescript_eslint_utils_ts_eslint.RuleModule<"dataLowercaseRequired" | "invalidPropOnTag" | "unknownProp" | "unknownPropWithStandardName", unknown[], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
35
39
  readonly "no-unsafe-iframe-sandbox": _typescript_eslint_utils_ts_eslint.RuleModule<"noUnsafeIframeSandbox", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
36
- readonly "no-unsafe-target-blank": _typescript_eslint_utils_ts_eslint.RuleModule<"noUnsafeTargetBlank", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
40
+ readonly "no-unsafe-target-blank": _typescript_eslint_utils_ts_eslint.RuleModule<MessageID$2, [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
37
41
  readonly "no-use-form-state": _typescript_eslint_utils_ts_eslint.RuleModule<"noUseFormState", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
38
42
  readonly "no-void-elements-with-children": _typescript_eslint_utils_ts_eslint.RuleModule<"noVoidElementsWithChildren", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
39
43
  };
@@ -65,7 +69,7 @@ declare const _default: {
65
69
  readonly "no-script-url": _typescript_eslint_utils_ts_eslint.RuleModule<"noScriptUrl", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
66
70
  readonly "no-unknown-property": _typescript_eslint_utils_ts_eslint.RuleModule<"dataLowercaseRequired" | "invalidPropOnTag" | "unknownProp" | "unknownPropWithStandardName", unknown[], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
67
71
  readonly "no-unsafe-iframe-sandbox": _typescript_eslint_utils_ts_eslint.RuleModule<"noUnsafeIframeSandbox", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
68
- readonly "no-unsafe-target-blank": _typescript_eslint_utils_ts_eslint.RuleModule<"noUnsafeTargetBlank", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
72
+ readonly "no-unsafe-target-blank": _typescript_eslint_utils_ts_eslint.RuleModule<MessageID$2, [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
69
73
  readonly "no-use-form-state": _typescript_eslint_utils_ts_eslint.RuleModule<"noUseFormState", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
70
74
  readonly "no-void-elements-with-children": _typescript_eslint_utils_ts_eslint.RuleModule<"noVoidElementsWithChildren", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener>;
71
75
  };
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { getDocsUrl, getSettingsFromContext, DEFAULT_ESLINT_REACT_SETTINGS } 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';
4
5
  import * as AST from '@eslint-react/ast';
5
6
  import { AST_NODE_TYPES } from '@typescript-eslint/types';
6
7
  import { compare } from 'compare-versions';
7
8
  import { RegExp, Reporter } from '@eslint-react/kit';
8
- import { unit } from '@eslint-react/eff';
9
9
 
10
10
  var __defProp = Object.defineProperty;
11
11
  var __export = (target, all) => {
@@ -44,7 +44,7 @@ var settings = {
44
44
 
45
45
  // package.json
46
46
  var name2 = "eslint-plugin-react-dom";
47
- var version = "2.0.0-next.52";
47
+ var version = "2.0.0-next.53";
48
48
  function createJsxElementResolver(context) {
49
49
  const { components, polymorphicPropName } = getSettingsFromContext(context);
50
50
  return {
@@ -89,6 +89,39 @@ var createRule = ESLintUtils.RuleCreator(getDocsUrl("dom"));
89
89
  function findCustomComponentProp(name3, props) {
90
90
  return props.findLast((a) => a.as === name3);
91
91
  }
92
+ function resolveAttribute(context, attributes, elementNode, attributeName) {
93
+ const customComponentProp = findCustomComponentProp(attributeName, attributes);
94
+ const propNameOnJsx = customComponentProp?.name ?? attributeName;
95
+ const attribute = ER.getAttribute(
96
+ context,
97
+ propNameOnJsx,
98
+ elementNode.openingElement.attributes,
99
+ context.sourceCode.getScope(elementNode)
100
+ );
101
+ if (attribute == null) {
102
+ return {
103
+ attribute: unit,
104
+ attributeName: propNameOnJsx,
105
+ attributeValue: unit,
106
+ attributeValueString: customComponentProp?.defaultValue
107
+ };
108
+ }
109
+ const attributeValue = ER.getAttributeValue(context, attribute, propNameOnJsx);
110
+ if (attributeValue.kind === "some" && typeof attributeValue.value === "string") {
111
+ return {
112
+ attribute,
113
+ attributeName: propNameOnJsx,
114
+ attributeValue,
115
+ attributeValueString: attributeValue.value
116
+ };
117
+ }
118
+ return {
119
+ attribute,
120
+ attributeName: propNameOnJsx,
121
+ attributeValue: unit,
122
+ attributeValueString: customComponentProp?.defaultValue ?? unit
123
+ };
124
+ }
92
125
 
93
126
  // src/rules/no-dangerously-set-innerhtml.ts
94
127
  var RULE_NAME = "no-dangerously-set-innerhtml";
@@ -322,6 +355,8 @@ function getFix(context, node) {
322
355
  ];
323
356
  };
324
357
  }
358
+
359
+ // src/rules/no-missing-button-type.ts
325
360
  var RULE_NAME6 = "no-missing-button-type";
326
361
  var RULE_FEATURES6 = [
327
362
  "FIX"
@@ -351,40 +386,26 @@ function create6(context) {
351
386
  JSXElement(node) {
352
387
  const { attributes, domElementType } = resolver.resolve(node);
353
388
  if (domElementType !== "button") return;
354
- const customComponentProp = findCustomComponentProp("type", attributes);
355
- const propNameOnJsx = customComponentProp?.name ?? "type";
356
- const attributeNode = ER.getAttribute(
357
- context,
358
- propNameOnJsx,
359
- node.openingElement.attributes,
360
- context.sourceCode.getScope(node)
361
- );
362
- if (attributeNode != null) {
363
- const attributeValue = ER.getAttributeValue(
364
- context,
365
- attributeNode,
366
- propNameOnJsx
367
- );
368
- if (attributeValue.kind !== "some" || typeof attributeValue.value !== "string") {
369
- context.report({
370
- messageId: "noMissingButtonType",
371
- node: attributeNode,
372
- suggest: getSuggest((type) => (fixer) => {
373
- return fixer.replaceText(attributeNode, `${propNameOnJsx}="${type}"`);
374
- })
375
- });
376
- }
377
- return;
378
- }
379
- if (typeof customComponentProp?.defaultValue !== "string") {
389
+ const typeAttribute = resolveAttribute(context, attributes, node, "type");
390
+ if (typeAttribute.attributeValueString != null) return;
391
+ if (typeAttribute.attribute == null) {
380
392
  context.report({
381
393
  messageId: "noMissingButtonType",
382
- node,
394
+ node: node.openingElement,
383
395
  suggest: getSuggest((type) => (fixer) => {
384
- return fixer.insertTextAfter(node.openingElement.name, ` ${propNameOnJsx}="${type}"`);
396
+ return fixer.insertTextAfter(node.openingElement.name, ` ${typeAttribute.attributeName}="${type}"`);
385
397
  })
386
398
  });
399
+ return;
387
400
  }
401
+ context.report({
402
+ messageId: "noMissingButtonType",
403
+ node: typeAttribute.attributeValue?.node ?? typeAttribute.attribute,
404
+ suggest: getSuggest((type) => (fixer) => {
405
+ if (typeAttribute.attribute == null) return null;
406
+ return fixer.replaceText(typeAttribute.attribute, `${typeAttribute.attributeName}="${type}"`);
407
+ })
408
+ });
388
409
  }
389
410
  };
390
411
  }
@@ -395,6 +416,8 @@ function getSuggest(getFix4) {
395
416
  fix: getFix4(type)
396
417
  }));
397
418
  }
419
+
420
+ // src/rules/no-missing-iframe-sandbox.ts
398
421
  var RULE_NAME7 = "no-missing-iframe-sandbox";
399
422
  var RULE_FEATURES7 = [
400
423
  "FIX"
@@ -424,52 +447,34 @@ function create7(context) {
424
447
  JSXElement(node) {
425
448
  const { attributes, domElementType } = resolver.resolve(node);
426
449
  if (domElementType !== "iframe") return;
427
- const customComponentProp = findCustomComponentProp("sandbox", attributes);
428
- const propNameOnJsx = customComponentProp?.name ?? "sandbox";
429
- const attributeNode = ER.getAttribute(
430
- context,
431
- propNameOnJsx,
432
- node.openingElement.attributes,
433
- context.sourceCode.getScope(node)
434
- );
435
- if (attributeNode != null) {
436
- const attributeValue = ER.getAttributeValue(
437
- context,
438
- attributeNode,
439
- propNameOnJsx
440
- );
441
- if (attributeValue.kind !== "some" || typeof attributeValue.value !== "string") {
442
- context.report({
443
- messageId: "noMissingIframeSandbox",
444
- node: attributeNode,
445
- suggest: [
446
- {
447
- messageId: "addIframeSandbox",
448
- data: { value: "" },
449
- fix(fixer) {
450
- return fixer.replaceText(attributeNode, `${propNameOnJsx}=""`);
451
- }
452
- }
453
- ]
454
- });
455
- }
456
- return;
457
- }
458
- if (typeof customComponentProp?.defaultValue !== "string") {
450
+ const sandboxAttribute = resolveAttribute(context, attributes, node, "sandbox");
451
+ if (sandboxAttribute.attributeValueString != null) return;
452
+ if (sandboxAttribute.attribute == null) {
459
453
  context.report({
460
454
  messageId: "noMissingIframeSandbox",
461
- node,
462
- suggest: [
463
- {
464
- messageId: "addIframeSandbox",
465
- data: { value: "" },
466
- fix(fixer) {
467
- return fixer.insertTextAfter(node.openingElement.name, ` ${propNameOnJsx}=""`);
468
- }
455
+ node: node.openingElement,
456
+ suggest: [{
457
+ messageId: "addIframeSandbox",
458
+ data: { value: "" },
459
+ fix(fixer) {
460
+ return fixer.insertTextAfter(node.openingElement.name, ` ${sandboxAttribute.attributeName}=""`);
469
461
  }
470
- ]
462
+ }]
471
463
  });
464
+ return;
472
465
  }
466
+ context.report({
467
+ messageId: "noMissingIframeSandbox",
468
+ node: sandboxAttribute.attributeValue?.node ?? sandboxAttribute.attribute,
469
+ suggest: [{
470
+ messageId: "addIframeSandbox",
471
+ data: { value: "" },
472
+ fix(fixer) {
473
+ if (sandboxAttribute.attribute == null) return null;
474
+ return fixer.replaceText(sandboxAttribute.attribute, `${sandboxAttribute.attributeName}=""`);
475
+ }
476
+ }]
477
+ });
473
478
  }
474
479
  };
475
480
  }
@@ -1743,12 +1748,14 @@ function testReactVersion(context, comparator, version2) {
1743
1748
  const { version: localVersion } = getSettingsFromContext(context);
1744
1749
  return compare(localVersion, version2, comparator);
1745
1750
  }
1751
+
1752
+ // src/rules/no-unsafe-iframe-sandbox.ts
1746
1753
  var RULE_NAME13 = "no-unsafe-iframe-sandbox";
1747
1754
  var RULE_FEATURES12 = [];
1748
1755
  var unsafeSandboxValues = [
1749
1756
  ["allow-scripts", "allow-same-origin"]
1750
1757
  ];
1751
- function hasSafeSandbox(value) {
1758
+ function isSafeSandbox(value) {
1752
1759
  if (typeof value !== "string") return false;
1753
1760
  return !unsafeSandboxValues.some((values) => {
1754
1761
  return values.every((v) => value.includes(v));
@@ -1776,40 +1783,22 @@ function create13(context) {
1776
1783
  JSXElement(node) {
1777
1784
  const { attributes, domElementType } = resolver.resolve(node);
1778
1785
  if (domElementType !== "iframe") return;
1779
- const customComponentProp = findCustomComponentProp("sandbox", attributes);
1780
- const propNameOnJsx = customComponentProp?.name ?? "sandbox";
1781
- const attributeNode = ER.getAttribute(
1782
- context,
1783
- propNameOnJsx,
1784
- node.openingElement.attributes,
1785
- context.sourceCode.getScope(node)
1786
- );
1787
- if (attributeNode != null) {
1788
- const attributeValue = ER.getAttributeValue(
1789
- context,
1790
- attributeNode,
1791
- propNameOnJsx
1792
- );
1793
- if (attributeValue.kind === "some" && !hasSafeSandbox(attributeValue.value)) {
1794
- context.report({
1795
- messageId: "noUnsafeIframeSandbox",
1796
- node: attributeNode
1797
- });
1798
- return;
1799
- }
1800
- }
1801
- if (customComponentProp?.defaultValue == null) return;
1802
- if (!hasSafeSandbox(customComponentProp.defaultValue)) {
1786
+ const sandboxAttribute = resolveAttribute(context, attributes, node, "sandbox");
1787
+ if (!isSafeSandbox(sandboxAttribute.attributeValueString)) {
1803
1788
  context.report({
1804
1789
  messageId: "noUnsafeIframeSandbox",
1805
- node
1790
+ node: sandboxAttribute.attributeValue?.node ?? sandboxAttribute.attribute ?? node
1806
1791
  });
1807
1792
  }
1808
1793
  }
1809
1794
  };
1810
1795
  }
1796
+
1797
+ // src/rules/no-unsafe-target-blank.ts
1811
1798
  var RULE_NAME14 = "no-unsafe-target-blank";
1812
- var RULE_FEATURES13 = [];
1799
+ var RULE_FEATURES13 = [
1800
+ "FIX"
1801
+ ];
1813
1802
  function isExternalLinkLike(value) {
1814
1803
  if (value == null) return false;
1815
1804
  return value.startsWith("https://") || /^(?:\w+:|\/\/)/u.test(value);
@@ -1825,7 +1814,10 @@ var no_unsafe_target_blank_default = createRule({
1825
1814
  description: 'Disallow `target="_blank"` without `rel="noreferrer noopener"`.',
1826
1815
  [Symbol.for("rule_features")]: RULE_FEATURES13
1827
1816
  },
1817
+ fixable: "code",
1818
+ hasSuggestions: true,
1828
1819
  messages: {
1820
+ addRelNoreferrerNoopener: `Add 'rel="noreferrer noopener"' to the link to prevent security risks.`,
1829
1821
  noUnsafeTargetBlank: `Using 'target="_blank"' on an external link without 'rel="noreferrer noopener"' is a security risk.`
1830
1822
  },
1831
1823
  schema: []
@@ -1840,35 +1832,44 @@ function create14(context) {
1840
1832
  JSXElement(node) {
1841
1833
  const { attributes, domElementType } = resolver.resolve(node);
1842
1834
  if (domElementType !== "a") return;
1843
- const elementScope = context.sourceCode.getScope(node);
1844
- const getAttributeStringValue = (name3) => {
1845
- const customComponentProp = findCustomComponentProp(name3, attributes);
1846
- const propNameOnJsx = customComponentProp?.name ?? name3;
1847
- const attributeNode = ER.getAttribute(
1848
- context,
1849
- propNameOnJsx,
1850
- node.openingElement.attributes,
1851
- elementScope
1852
- );
1853
- if (attributeNode == null) return customComponentProp?.defaultValue;
1854
- const attributeValue = ER.getAttributeValue(context, attributeNode, propNameOnJsx);
1855
- if (attributeValue.kind === "some" && typeof attributeValue.value === "string") {
1856
- return attributeValue.value;
1857
- }
1858
- return unit;
1859
- };
1860
- if (getAttributeStringValue("target") !== "_blank") {
1835
+ const targetAttribute = resolveAttribute(context, attributes, node, "target");
1836
+ if (targetAttribute.attributeValueString !== "_blank") {
1837
+ return;
1838
+ }
1839
+ const hrefAttribute = resolveAttribute(context, attributes, node, "href");
1840
+ if (!isExternalLinkLike(hrefAttribute.attributeValueString)) {
1861
1841
  return;
1862
1842
  }
1863
- if (!isExternalLinkLike(getAttributeStringValue("href"))) {
1843
+ const relAttribute = resolveAttribute(context, attributes, node, "rel");
1844
+ if (isSafeRel(relAttribute.attributeValueString)) {
1864
1845
  return;
1865
1846
  }
1866
- if (isSafeRel(getAttributeStringValue("rel"))) {
1847
+ if (relAttribute.attribute == null) {
1848
+ context.report({
1849
+ messageId: "noUnsafeTargetBlank",
1850
+ node: node.openingElement,
1851
+ suggest: [{
1852
+ messageId: "addRelNoreferrerNoopener",
1853
+ fix(fixer) {
1854
+ return fixer.insertTextAfter(
1855
+ node.openingElement.name,
1856
+ ` ${relAttribute.attributeName}="noreferrer noopener"`
1857
+ );
1858
+ }
1859
+ }]
1860
+ });
1867
1861
  return;
1868
1862
  }
1869
1863
  context.report({
1870
1864
  messageId: "noUnsafeTargetBlank",
1871
- node
1865
+ node: relAttribute.attributeValue?.node ?? relAttribute.attribute,
1866
+ suggest: [{
1867
+ messageId: "addRelNoreferrerNoopener",
1868
+ fix(fixer) {
1869
+ if (relAttribute.attribute == null) return null;
1870
+ return fixer.replaceText(relAttribute.attribute, `${relAttribute.attributeName}="noreferrer noopener"`);
1871
+ }
1872
+ }]
1872
1873
  });
1873
1874
  }
1874
1875
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-dom",
3
- "version": "2.0.0-next.52",
3
+ "version": "2.0.0-next.53",
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.7.1",
44
- "@eslint-react/ast": "2.0.0-next.52",
45
- "@eslint-react/core": "2.0.0-next.52",
46
- "@eslint-react/eff": "2.0.0-next.52",
47
- "@eslint-react/kit": "2.0.0-next.52",
48
- "@eslint-react/var": "2.0.0-next.52",
49
- "@eslint-react/shared": "2.0.0-next.52"
44
+ "@eslint-react/ast": "2.0.0-next.53",
45
+ "@eslint-react/eff": "2.0.0-next.53",
46
+ "@eslint-react/kit": "2.0.0-next.53",
47
+ "@eslint-react/core": "2.0.0-next.53",
48
+ "@eslint-react/shared": "2.0.0-next.53",
49
+ "@eslint-react/var": "2.0.0-next.53"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/react": "^19.1.8",