diff --git a/CHANGELOG.md b/CHANGELOG.md
index f2e96e9af8..77332e041d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
### Added
* [`destructuring-assignment`]: add option `destructureInSignature` ([#3235][] @golopot)
* [`no-unknown-property`]: Allow crossOrigin on image tag (SVG) ([#3251][] @zpao)
+* [`jsx-tag-spacing`]: Add `multiline-always` option ([#3260][] @Nokel81)
### Fixed
* [`hook-use-state`]: Allow UPPERCASE setState setter prefixes ([#3244][] @duncanbeevers)
@@ -18,6 +19,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
* [Refactor] fix linter errors ([#3261][] @golopot)
[#3261]: https://github.com/yannickcr/eslint-plugin-react/pull/3261
+[#3260]: https://github.com/yannickcr/eslint-plugin-react/pull/3260
[#3254]: https://github.com/yannickcr/eslint-plugin-react/pull/3254
[#3251]: https://github.com/yannickcr/eslint-plugin-react/pull/3251
[#3244]: https://github.com/yannickcr/eslint-plugin-react/pull/3244
diff --git a/docs/rules/jsx-tag-spacing.md b/docs/rules/jsx-tag-spacing.md
index fbf47aea8d..9651370a54 100644
--- a/docs/rules/jsx-tag-spacing.md
+++ b/docs/rules/jsx-tag-spacing.md
@@ -62,7 +62,7 @@ Examples of **correct** code for this rule, when configured with `{ "closingSlas
### `beforeSelfClosing`
-This check can be set to `"always"`, `"never"` or `"allow"` (to disable it).
+This check can be set to `"always"`, `"never"`, `"multiline-always"`, or `"allow"` (to disable it).
If it is `"always"`, the check warns whenever a space is missing before the closing bracket. If `"never"` then it warns if a space is present before the closing bracket. The default value of this check is `"always"`.
@@ -102,6 +102,26 @@ Examples of **correct** code for this rule, when configured with `{ "beforeSelfC
/>
```
+Examples of **incorrect** code for this rule, when configured with `{ "beforeSelfClosing": "multiline-always" }`:
+
+```jsx
+
+
+```
+
+Examples of **correct** code for this rule, when configured with `{ "beforeSelfClosing": "multiline-always" }`:
+
+```jsx
+
+```
+
### `afterOpening`
This check can be set to `"always"`, `"never"`, `"allow-multiline"` or `"allow"` (to disable it).
@@ -179,7 +199,7 @@ Examples of **correct** code for this rule, when configured with `{ "afterOpenin
### `beforeClosing`
-This check can be set to `"always"`, `"never"`, or `"allow"` (to disable it).
+This check can be set to `"always"`, `"never"`, `"multiline-always"`, or `"allow"` (to disable it).
If it is `"always"` the check warns whenever whitespace is missing before the closing bracket of a JSX opening element or whenever a space is missing before the closing bracket closing element. If `"never"`, then it warns if a space is present before the closing bracket of either a JSX opening element or closing element. This rule will never warn for self closing JSX elements. The default value of this check is `"allow"`.
@@ -219,6 +239,31 @@ Examples of **correct** code for this rule, when configured with `{ "beforeClosi
```
+Examples of **incorrect** code for this rule, when configured with `{ "beforeClosing": "multiline-always" }`:
+
+```jsx
+
+
+
+ Goodbye
+
+```
+
+Examples of **correct** code for this rule, when configured with `{ "beforeClosing": "multiline-always" }`:
+
+```jsx
+
+ Goodbye
+
+```
+
## When Not To Use It
You can turn this rule off if you are not concerned with the consistency of spacing in or around JSX brackets.
diff --git a/lib/rules/jsx-tag-spacing.js b/lib/rules/jsx-tag-spacing.js
index 546dd082cf..9db4a8923a 100644
--- a/lib/rules/jsx-tag-spacing.js
+++ b/lib/rules/jsx-tag-spacing.js
@@ -16,10 +16,12 @@ const messages = {
closeSlashNeedSpace: 'Whitespace is required between `<` and `/`; write `< /`',
beforeSelfCloseNoSpace: 'A space is forbidden before closing bracket',
beforeSelfCloseNeedSpace: 'A space is required before closing bracket',
+ beforeSelfCloseNeedNewline: 'A newline is required before closing bracket',
afterOpenNoSpace: 'A space is forbidden after opening bracket',
afterOpenNeedSpace: 'A space is required after opening bracket',
beforeCloseNoSpace: 'A space is forbidden before closing bracket',
beforeCloseNeedSpace: 'Whitespace is required before closing bracket',
+ beforeCloseNeedNewline: 'A newline is required before closing bracket',
};
// ------------------------------------------------------------------------------
@@ -99,6 +101,18 @@ function validateBeforeSelfClosing(context, node, option) {
const leftToken = getTokenBeforeClosingBracket(node);
const closingSlash = sourceCode.getTokenAfter(leftToken);
+ if (node.loc.start.line !== node.loc.end.line && option === 'multiline-always') {
+ if (leftToken.loc.end.line === closingSlash.loc.start.line) {
+ report(context, messages.beforeSelfCloseNeedNewline, 'beforeSelfCloseNeedNewline', {
+ node,
+ loc: leftToken.loc.end,
+ fix(fixer) {
+ return fixer.insertTextBefore(closingSlash, '\n');
+ },
+ });
+ }
+ }
+
if (leftToken.loc.end.line !== closingSlash.loc.start.line) {
return;
}
@@ -170,6 +184,18 @@ function validateBeforeClosing(context, node, option) {
const closingToken = lastTokens[1];
const leftToken = lastTokens[0];
+ if (node.loc.start.line !== node.loc.end.line && option === 'multiline-always') {
+ if (leftToken.loc.end.line === closingToken.loc.start.line) {
+ report(context, messages.beforeCloseNeedNewline, 'beforeCloseNeedNewline', {
+ node,
+ loc: leftToken.loc.end,
+ fix(fixer) {
+ return fixer.insertTextBefore(closingToken, '\n');
+ },
+ });
+ }
+ }
+
if (leftToken.loc.start.line !== closingToken.loc.start.line) {
return;
}
@@ -233,13 +259,13 @@ module.exports = {
enum: ['always', 'never', 'allow'],
},
beforeSelfClosing: {
- enum: ['always', 'never', 'allow'],
+ enum: ['always', 'multiline-always', 'never', 'allow'],
},
afterOpening: {
enum: ['always', 'allow-multiline', 'never', 'allow'],
},
beforeClosing: {
- enum: ['always', 'never', 'allow'],
+ enum: ['always', 'multiline-always', 'never', 'allow'],
},
},
default: optionDefaults,
diff --git a/tests/lib/rules/jsx-tag-spacing.js b/tests/lib/rules/jsx-tag-spacing.js
index 66899d6a90..e2a9c759a2 100644
--- a/tests/lib/rules/jsx-tag-spacing.js
+++ b/tests/lib/rules/jsx-tag-spacing.js
@@ -114,6 +114,49 @@ ruleTester.run('jsx-tag-spacing', rule, {
code: '',
options: beforeSelfClosingOptions('never'),
},
+ {
+ code: '',
+ options: beforeSelfClosingOptions('multiline-always'),
+ },
+ {
+ code: '',
+ options: beforeSelfClosingOptions('multiline-always'),
+ },
+ {
+ code: '',
+ options: beforeSelfClosingOptions('multiline-always'),
+ },
+ {
+ code: '',
+ options: beforeSelfClosingOptions('multiline-always'),
+ },
+ {
+ code: `
+
+ hello
+
+ `,
+ options: beforeClosingOptions('multiline-always'),
+ },
+ {
+ code: `
+
+ hello
+
+ `,
+ options: beforeClosingOptions('multiline-always'),
+ },
+ {
+ code: `
+
+ `,
+ options: beforeSelfClosingOptions('multiline-always'),
+ },
{
code: '',
options: beforeSelfClosingOptions('never'),
@@ -302,6 +345,64 @@ ruleTester.run('jsx-tag-spacing', rule, {
options: beforeSelfClosingOptions('never'),
errors: [{ messageId: 'beforeSelfCloseNoSpace' }],
},
+ {
+ code: `
+ `,
+ output: `
+ `,
+ options: beforeSelfClosingOptions('multiline-always'),
+ errors: [{ messageId: 'beforeSelfCloseNeedNewline' }],
+ },
+ {
+ code: `
+ `,
+ output: `
+ `,
+ options: beforeSelfClosingOptions('multiline-always'),
+ errors: [{ messageId: 'beforeSelfCloseNeedNewline' }],
+ },
+ {
+ code: `
+
+ hello
+
+ `,
+ output: `
+
+ hello
+
+ `,
+ options: beforeClosingOptions('multiline-always'),
+ errors: [{ messageId: 'beforeCloseNeedNewline' }],
+ },
+ {
+ code: `
+
+ hello
+
+ `,
+ output: `
+
+ hello
+
+ `,
+ options: beforeClosingOptions('multiline-always'),
+ errors: [{ messageId: 'beforeCloseNeedNewline' }],
+ },
{
code: '',
output: '',