diff --git a/apps/website/content/docs/rules/meta.json b/apps/website/content/docs/rules/meta.json index 3fe23e91c..950857376 100644 --- a/apps/website/content/docs/rules/meta.json +++ b/apps/website/content/docs/rules/meta.json @@ -2,6 +2,7 @@ "pages": [ "overview", "---X Rules---", + "jsx-key-before-spread", "jsx-no-duplicate-props", "jsx-no-undef", "jsx-uses-react", diff --git a/apps/website/content/docs/rules/overview.mdx b/apps/website/content/docs/rules/overview.mdx index cffdc7f32..1e180a92c 100644 --- a/apps/website/content/docs/rules/overview.mdx +++ b/apps/website/content/docs/rules/overview.mdx @@ -31,6 +31,7 @@ The `jsx-*` rules check for issues exclusive to JSX syntax, which are absent fro | Rule | ✅ | 🌟 | Description | `react` | | :----------------------------------------------------------------------------------- | :-- | :-------: | :-------------------------------------------------------------------------------------------------- | :------: | +| [`jsx-key-before-spread`](./jsx-key-before-spread) | 1️⃣ | | Enforces that the `key` attribute is placed before the spread attribute in JSX elements | | | [`jsx-no-duplicate-props`](./jsx-no-duplicate-props) | 1️⃣ | | Disallow duplicate props in JSX elements | | | [`jsx-no-undef`](./jsx-no-undef) | 0️⃣ | | Disallow undefined variables in JSX elements | | | [`jsx-uses-react`](./jsx-uses-react) | 1️⃣ | | Marks React variables as used when JSX is used | | diff --git a/packages/plugins/eslint-plugin-react-x/src/configs/recommended.ts b/packages/plugins/eslint-plugin-react-x/src/configs/recommended.ts index 33f916537..7e1deae04 100644 --- a/packages/plugins/eslint-plugin-react-x/src/configs/recommended.ts +++ b/packages/plugins/eslint-plugin-react-x/src/configs/recommended.ts @@ -4,6 +4,7 @@ import { DEFAULT_ESLINT_REACT_SETTINGS } from "@eslint-react/shared"; export const name = "react-x/recommended"; export const rules = { + "react-x/jsx-key-before-spread": "warn", "react-x/jsx-no-duplicate-props": "warn", "react-x/jsx-uses-react": "warn", "react-x/jsx-uses-vars": "warn", diff --git a/packages/plugins/eslint-plugin-react-x/src/plugin.ts b/packages/plugins/eslint-plugin-react-x/src/plugin.ts index 5d7bf3f09..d07b2bef1 100644 --- a/packages/plugins/eslint-plugin-react-x/src/plugin.ts +++ b/packages/plugins/eslint-plugin-react-x/src/plugin.ts @@ -1,6 +1,7 @@ import { name, version } from "../package.json"; import avoidShorthandBoolean from "./rules/avoid-shorthand-boolean"; import avoidShorthandFragment from "./rules/avoid-shorthand-fragment"; +import jsxKeyBeforeSpread from "./rules/jsx-key-before-spread"; import jsxNoDuplicateProps from "./rules/jsx-no-duplicate-props"; import jsxNoUndef from "./rules/jsx-no-undef"; import jsxUsesReact from "./rules/jsx-uses-react"; @@ -116,6 +117,7 @@ export const plugin = { "prefer-shorthand-fragment": preferShorthandFragment, // Part: JSX only rules + "jsx-key-before-spread": jsxKeyBeforeSpread, "jsx-no-duplicate-props": jsxNoDuplicateProps, "jsx-no-undef": jsxNoUndef, "jsx-uses-react": jsxUsesReact, diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/jsx-key-before-spread.md b/packages/plugins/eslint-plugin-react-x/src/rules/jsx-key-before-spread.md new file mode 100644 index 000000000..2a7107097 --- /dev/null +++ b/packages/plugins/eslint-plugin-react-x/src/rules/jsx-key-before-spread.md @@ -0,0 +1,51 @@ +--- +title: jsx-key-before-spread +--- + +**Full Name in `eslint-plugin-react-x`** + +```sh copy +react-x/jsx-key-before-spread +``` + +**Full Name in `@eslint-react/eslint-plugin`** + +```sh copy +@eslint-react/jsx-key-before-spread +``` + +**Presets** + +- `x` +- `recommended` +- `recommended-typescript` +- `recommended-type-checked` + +## Description + +Enforces that the `key` attribute is placed before the spread attribute in JSX elements. + +When using the JSX automatic runtime, `key` is a special attribute in the JSX transform. See the [Babel repl](https://babeljs.io/repl#?browsers=last%202%20chrome%20versions&build=&builtIns=false&corejs=3.21&spec=false&loose=false&code_lz=DwEwlgbgBA1gpgTwLwCICMKoG8B0eAOATgPb4DOAvlAPQB8A3AFCiTZ45GmWyKoBMmOkxbR4yFAGZMAYwA2AQzJkAcvIC2cVIIbNw0OYpXrNKTGNRSaDIA&forceAllTransforms=false&modules=false&shippedProposals=false&evaluate=true&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=react&prettier=false&targets=&version=7.27.0&externalPlugins=&assumptions=%7B%7D) and [TypeScript playground](https://www.typescriptlang.org/play/?target=99&jsx=4#code/DwEwlgbgBA1gpgTwLwCICMKoG8B0eAOATgPb4DOAvlAPQB8A3ALABQok2eORplsiqAJkx0mrcNHjIUAZkwBjADYBDMmQBySgLZxUwhizbRFK9Vp0pMk1ABY99IA) + +If the `key` prop is _before_ any spread props, it is passed as the `key` argument of the `_jsx` / `_jsxs` / `_jsxDev` function. But if the `key` prop is _after_ spread props, The compiler uses `createElement` instead and passes `key` as a regular prop. + +## Examples + +### Failing + +```tsx +
; +``` + +### Passing + +```tsx +; +; +; +``` + +## Implementation + +- [Rule source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/jsx-key-before-spread.ts) +- [Test source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/jsx-key-before-spread.spec.ts) diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/jsx-key-before-spread.spec.ts b/packages/plugins/eslint-plugin-react-x/src/rules/jsx-key-before-spread.spec.ts new file mode 100644 index 000000000..843075404 --- /dev/null +++ b/packages/plugins/eslint-plugin-react-x/src/rules/jsx-key-before-spread.spec.ts @@ -0,0 +1,64 @@ +import tsx from "dedent"; + +import { allValid, ruleTester } from "../../../../../test"; +import rule, { RULE_NAME } from "./jsx-key-before-spread"; + +ruleTester.run(RULE_NAME, rule, { + invalid: [ + { + code: tsx` + const App = (props) => { + return [ +