Skip to content

Commit 8f49aaf

Browse files
committed
feat(plugins/x): add 'jsx-no-duplicate-props'
1 parent 20068b0 commit 8f49aaf

File tree

4 files changed

+82
-0
lines changed

4 files changed

+82
-0
lines changed

packages/plugins/eslint-plugin-react-x/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { name, version } from "../package.json";
55
import avoidShorthandBoolean from "./rules/avoid-shorthand-boolean";
66
import avoidShorthandFragment from "./rules/avoid-shorthand-fragment";
77
import forwardRefUsingRef from "./rules/ensure-forward-ref-using-ref";
8+
import jsxNoDuplicateProps from "./rules/jsx-no-duplicate-props";
89
import jsxUsesVars from "./rules/jsx-uses-vars";
910
import noAccessStateInSetstate from "./rules/no-access-state-in-setstate";
1011
import noArrayIndexKey from "./rules/no-array-index-key";
@@ -71,6 +72,7 @@ export default {
7172
"avoid-shorthand-boolean": avoidShorthandBoolean,
7273
"avoid-shorthand-fragment": avoidShorthandFragment,
7374
"ensure-forward-ref-using-ref": forwardRefUsingRef,
75+
"jsx-no-duplicate-props": jsxNoDuplicateProps,
7476
"jsx-uses-vars": jsxUsesVars,
7577
"no-access-state-in-setstate": noAccessStateInSetstate,
7678
"no-array-index-key": noArrayIndexKey,
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { allValid, ruleTester } from "../../../../../test";
2+
import rule, { RULE_NAME } from "./jsx-no-duplicate-props";
3+
4+
ruleTester.run(RULE_NAME, rule, {
5+
invalid: [
6+
{
7+
code: /* tsx */ `<div a="1" a="2" />;`,
8+
errors: [{ messageId: "jsxNoDuplicateProps" }],
9+
},
10+
{
11+
code: /* tsx */ `<div a="1" b="2" a="3" />;`,
12+
errors: [{ messageId: "jsxNoDuplicateProps" }],
13+
},
14+
{
15+
code: /* tsx */ `<div a="1" {...b} a="2" />;`,
16+
errors: [{ messageId: "jsxNoDuplicateProps" }],
17+
},
18+
{
19+
code: /* tsx */ `<div a="1" {...a} {...b} a="2" />;`,
20+
errors: [{ messageId: "jsxNoDuplicateProps" }],
21+
},
22+
],
23+
valid: [
24+
...allValid,
25+
/* tsx */ `const a = <div a="1" aa="2" />;`,
26+
/* tsx */ `const a = <div a="1" aa="2"><span a="1" aa="2" /></div>;`,
27+
/* tsx */ `const a = <div a="1" b="2" />;`,
28+
/* tsx */ `const a = <div a="1" {...b} />;`,
29+
/* tsx */ `const a = <div {...a} {...b} />;`,
30+
],
31+
});
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { isString } from "@eslint-react/tools";
2+
import { AST_NODE_TYPES } from "@typescript-eslint/types";
3+
import type { CamelCase } from "string-ts";
4+
5+
import { createRule } from "../utils";
6+
7+
export const RULE_NAME = "jsx-no-duplicate-props";
8+
9+
export type MessageID = CamelCase<typeof RULE_NAME>;
10+
11+
export default createRule<[], MessageID>({
12+
meta: {
13+
type: "problem",
14+
docs: {
15+
description: "disallow duplicate props",
16+
},
17+
messages: {
18+
jsxNoDuplicateProps: "Duplicate prop '{{name}}'",
19+
},
20+
schema: [],
21+
},
22+
name: RULE_NAME,
23+
create(context) {
24+
return {
25+
JSXOpeningElement(node) {
26+
const props: string[] = [];
27+
for (const attr of node.attributes) {
28+
if (attr.type === AST_NODE_TYPES.JSXSpreadAttribute) continue;
29+
const name = attr.name.name;
30+
if (!isString(name)) continue;
31+
if (!props.includes(name)) {
32+
props.push(name);
33+
continue;
34+
}
35+
context.report({
36+
messageId: "jsxNoDuplicateProps",
37+
node: attr,
38+
data: { name },
39+
});
40+
}
41+
},
42+
};
43+
},
44+
defaultOptions: [],
45+
});

packages/plugins/eslint-plugin/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const allPreset = {
1818
"avoid-shorthand-boolean": "warn",
1919
"avoid-shorthand-fragment": "warn",
2020
"ensure-forward-ref-using-ref": "warn",
21+
"jsx-no-duplicate-props": "warn",
2122
"jsx-uses-vars": "warn",
2223
"no-access-state-in-setstate": "error",
2324
"no-array-index-key": "warn",
@@ -100,6 +101,7 @@ const corePreset = {
100101
"ensure-forward-ref-using-ref": "warn",
101102
// "avoid-shorthand-boolean": "warn",
102103
// "avoid-shorthand-fragment": "warn",
104+
"jsx-no-duplicate-props": "warn",
103105
"jsx-uses-vars": "warn",
104106
"no-access-state-in-setstate": "error",
105107
"no-array-index-key": "warn",
@@ -183,6 +185,8 @@ const recommendedPreset = {
183185
const recommendedTypeScriptPreset = {
184186
...recommendedPreset,
185187
"dom/no-unknown-property": "off",
188+
"jsx-no-duplicate-props": "off",
189+
"jsx-uses-vars": "off",
186190
} as const satisfies RulePreset;
187191

188192
const recommendedTypeCheckedPreset = {

0 commit comments

Comments
 (0)