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.
- package/dist/index.js +91 -105
- 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.
|
|
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 {
|
|
65
|
+
const { polymorphicPropName } = getSettingsFromContext(context);
|
|
56
66
|
return { resolve(node) {
|
|
57
|
-
const
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
jsxElementType: name$2
|
|
69
|
+
domElementType: elementName,
|
|
70
|
+
jsxElementType: elementName
|
|
63
71
|
};
|
|
64
|
-
if (
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
if (
|
|
68
|
-
const polymorphicPropValue = ER.
|
|
69
|
-
|
|
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:
|
|
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
|
|
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 {
|
|
354
|
+
const { domElementType } = resolver.resolve(node);
|
|
385
355
|
if (domElementType !== "button") return;
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
if (typeAttribute
|
|
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, `
|
|
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
|
|
371
|
+
node: typeAttribute,
|
|
401
372
|
suggest: getSuggest((type) => (fixer) => {
|
|
402
|
-
|
|
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 {
|
|
412
|
+
const { domElementType } = resolver.resolve(node);
|
|
443
413
|
if (domElementType !== "iframe") return;
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
if (sandboxAttribute
|
|
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, `
|
|
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:
|
|
434
|
+
node: sandboxAttributeValue.node ?? sandboxAttribute,
|
|
463
435
|
suggest: [{
|
|
464
436
|
messageId: "addIframeSandbox",
|
|
465
437
|
data: { value: "" },
|
|
466
438
|
fix(fixer) {
|
|
467
|
-
if (
|
|
468
|
-
return fixer.replaceText(sandboxAttribute
|
|
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
|
|
662
|
-
if (
|
|
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
|
|
692
|
-
const
|
|
693
|
-
if (
|
|
694
|
-
|
|
695
|
-
if (
|
|
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 {
|
|
1834
|
+
const { domElementType } = resolver.resolve(node);
|
|
1870
1835
|
if (domElementType !== "iframe") return;
|
|
1871
|
-
const
|
|
1872
|
-
|
|
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:
|
|
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
|
|
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
|
|
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 {
|
|
1894
|
+
const { domElementType } = resolver.resolve(node);
|
|
1914
1895
|
if (domElementType !== "a") return;
|
|
1915
|
-
const
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
if (
|
|
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, `
|
|
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
|
|
1923
|
+
node: relAttribute,
|
|
1937
1924
|
suggest: [{
|
|
1938
1925
|
messageId: "addRelNoreferrerNoopener",
|
|
1939
1926
|
fix(fixer) {
|
|
1940
|
-
|
|
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.
|
|
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.
|
|
45
|
-
"@eslint-react/
|
|
46
|
-
"@eslint-react/
|
|
47
|
-
"@eslint-react/
|
|
48
|
-
"@eslint-react/
|
|
49
|
-
"@eslint-react/
|
|
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",
|