feat: add react/react-jsx-no-duplicate-props
MODIFY
packages/@romejs/compiler/lint/rules/index.ts
packages/@romejs/compiler/lint/rules/index.ts
@@ -75,6 +75,7 @@ import tabindexNoPositive from "./jsx-a11y/tabindexNoPositive";
import buttonHasType from "./react/buttonHasType";
import jsxKey from "./react/jsxKey";
import jsxNoCommentText from "./react/jsxNoCommentText";
+import jsxNoDuplicateProps from "./react/jsxNoDuplicateProps";
import noChildrenProp from "./react/noChildrenProp";
import noDanger from "./react/noDanger";
import noDangerWithChildren from "./react/noDangerWithChildren";
@@ -160,6 +161,7 @@ export const lintTransforms = [
buttonHasType,
jsxKey,
jsxNoCommentText,
+ jsxNoDuplicateProps,
noChildrenProp,
noDanger,
noDangerWithChildren,

ADD
packages/@romejs/compiler/lint/rules/react/jsxNoDuplicateProps.test.ts
+import {test} from "rome";
+import {testLintMultiple} from "../testHelpers";
+
+test(
+ "jsx no duplicate props",
+ async (t) => {
+ await testLintMultiple(
+ t,
+ [
+ // INVALID
+ '<Hello foo="bar" foo="baz" />',
+ '<div style="{}" style="{}" id="foo" id="bar" />',
+ // VALID
+ '<Hello foo="bar" />',
+ '<div style="{}" />',
+ ],
+ {category: "lint/react/jsxNoDuplicateProps"},
+ );
+ },
+);

ADD
packages/@romejs/compiler/lint/rules/react/jsxNoDuplicateProps.ts
+import {Path, TransformExitResult} from "@romejs/compiler";
+import {descriptions} from "@romejs/diagnostics";
+import {DiagnosticsDuplicateHelper} from "@romejs/compiler/lib/DiagnosticsDuplicateHelper";
+import {isJSXElement} from "@romejs/js-ast-utils";
+import {JSXAttribute} from "@romejs/ast";
+
+function getAttributeKey(node: JSXAttribute): string {
+ const name = node.name.name;
+ return typeof name === "string" ? name : name.name;
+}
+
+export default {
+ name: "jsxNoDuplicateProps",
+
+ enter(path: Path): TransformExitResult {
+ const {context, node} = path;
+
+ if (!isJSXElement(node)) {
+ return node;
+ }
+
+ const duplicates = new DiagnosticsDuplicateHelper(
+ context,
+ descriptions.LINT.REACT_JSX_NO_DUPLICATE_PROPS,
+ );
+
+ for (const attr of node.attributes) {
+ if (attr.type === "JSXAttribute") {
+ duplicates.addLocation(getAttributeKey(attr), attr.loc);
+ }
+ }
+
+ duplicates.process();
+
+ return node;
+ },
+};

MODIFY
packages/@romejs/diagnostics/categories.ts
packages/@romejs/diagnostics/categories.ts
@@ -161,6 +161,7 @@ type LintDiagnosticCategory =
| "lint/react/buttonHasType"
| "lint/react/jsxKey"
| "lint/react/jsxNoCommentText"
+ | "lint/react/jsxNoDuplicateProps"
| "lint/react/noChildrenProp"
| "lint/react/noDanger"
| "lint/react/noDangerWithChildren"

MODIFY
packages/@romejs/diagnostics/descriptions/lint.ts
packages/@romejs/diagnostics/descriptions/lint.ts
@@ -16,6 +16,10 @@ import {buildSuggestionAdvice} from "../helpers";
import {createDiagnosticsCategory, orJoin} from "./index";
export const lint = createDiagnosticsCategory({
+ REACT_JSX_NO_DUPLICATE_PROPS: (key: string) => ({
+ category: "lint/react/jsxNoDuplicateProps",
+ message: `React does not support duplicate props. The <emphasis>${key}</emphasis> prop is duplicated.`,
+ },
REACT_NO_STRING_REFS: (details: string) => ({
category: "lint/react/noStringRefs",
message: `Using ${details} is a deprecated pattern.`,

feat: add react/react-jsx-no-duplicate-props (#502)

Co-authored-by: Zack Argyle <zack.argyle@gmail.com>

Co-authored-by: Zack Argyle <zack.argyle@gmail.com>
Co-authored-by: Sebastian <853712+sebmck@users.noreply.github.com>