diff --git a/CHANGELOG.md b/CHANGELOG.md
index 416ca2d677..a79673751f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +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)
+* [`jsx-tag-spacing`]: Add `multiline-always` option ([#3260][], [#3264][] @Nokel81)
* [`function-component-definition`]: replace `var` by `const` in certain situations ([#3248][] @JohnBerd @SimeonC)
* add [`jsx-no-leaked-render`] ([#3203][] @Belco90)
* [`require-default-props`]: add option `functions` ([#3249][] @nix6839)
@@ -45,6 +45,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
[#3267]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3267
[#3266]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3266
[#3265]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3265
+[#3264]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3264
[#3261]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3261
[#3260]: https://github.jsx-eslintckcr/eslint-plugin-react/pull/3260
[#3259]: https://githubjsx-eslintickcr/eslint-plugin-react/pull/3259
diff --git a/lib/rules/jsx-tag-spacing.js b/lib/rules/jsx-tag-spacing.js
index 9db4a8923a..421fb7fbc2 100644
--- a/lib/rules/jsx-tag-spacing.js
+++ b/lib/rules/jsx-tag-spacing.js
@@ -101,7 +101,7 @@ 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 (node.loc.start.line !== node.loc.end.line && option === 'proportional-always') {
if (leftToken.loc.end.line === closingSlash.loc.start.line) {
report(context, messages.beforeSelfCloseNeedNewline, 'beforeSelfCloseNeedNewline', {
node,
@@ -110,6 +110,7 @@ function validateBeforeSelfClosing(context, node, option) {
return fixer.insertTextBefore(closingSlash, '\n');
},
});
+ return;
}
}
@@ -117,7 +118,9 @@ function validateBeforeSelfClosing(context, node, option) {
return;
}
- if (option === 'always' && !sourceCode.isSpaceBetweenTokens(leftToken, closingSlash)) {
+ const adjacent = !sourceCode.isSpaceBetweenTokens(leftToken, closingSlash);
+
+ if ((option === 'always' || option === 'proportional-always') && adjacent) {
report(context, messages.beforeSelfCloseNeedSpace, 'beforeSelfCloseNeedSpace', {
node,
loc: closingSlash.loc.start,
@@ -125,7 +128,7 @@ function validateBeforeSelfClosing(context, node, option) {
return fixer.insertTextBefore(closingSlash, ' ');
},
});
- } else if (option === 'never' && sourceCode.isSpaceBetweenTokens(leftToken, closingSlash)) {
+ } else if (option === 'never' && !adjacent) {
report(context, messages.beforeSelfCloseNoSpace, 'beforeSelfCloseNoSpace', {
node,
loc: closingSlash.loc.start,
@@ -180,11 +183,12 @@ function validateBeforeClosing(context, node, option) {
// Don't enforce this rule for self closing tags
if (!node.selfClosing) {
const sourceCode = context.getSourceCode();
- const lastTokens = sourceCode.getLastTokens(node, 2);
- const closingToken = lastTokens[1];
- const leftToken = lastTokens[0];
+ const leftToken = option === 'proportional-always'
+ ? getTokenBeforeClosingBracket(node)
+ : sourceCode.getLastTokens(node, 2)[0];
+ const closingToken = sourceCode.getTokenAfter(leftToken);
- if (node.loc.start.line !== node.loc.end.line && option === 'multiline-always') {
+ if (node.loc.start.line !== node.loc.end.line && option === 'proportional-always') {
if (leftToken.loc.end.line === closingToken.loc.start.line) {
report(context, messages.beforeCloseNeedNewline, 'beforeCloseNeedNewline', {
node,
@@ -193,6 +197,7 @@ function validateBeforeClosing(context, node, option) {
return fixer.insertTextBefore(closingToken, '\n');
},
});
+ return;
}
}
@@ -224,6 +229,17 @@ function validateBeforeClosing(context, node, option) {
return fixer.insertTextBefore(closingToken, ' ');
},
});
+ } else if (option === 'proportional-always' && node.type === 'JSXOpeningElement' && adjacent !== (node.loc.start.line === node.loc.end.line)) {
+ report(context, messages.beforeCloseNeedSpace, 'beforeCloseNeedSpace', {
+ node,
+ loc: {
+ start: leftToken.loc.end,
+ end: closingToken.loc.start,
+ },
+ fix(fixer) {
+ return fixer.insertTextBefore(closingToken, ' ');
+ },
+ });
}
}
}
@@ -259,13 +275,13 @@ module.exports = {
enum: ['always', 'never', 'allow'],
},
beforeSelfClosing: {
- enum: ['always', 'multiline-always', 'never', 'allow'],
+ enum: ['always', 'proportional-always', 'never', 'allow'],
},
afterOpening: {
enum: ['always', 'allow-multiline', 'never', 'allow'],
},
beforeClosing: {
- enum: ['always', 'multiline-always', 'never', 'allow'],
+ enum: ['always', 'proportional-always', 'never', 'allow'],
},
},
default: optionDefaults,
diff --git a/lib/util/getTokenBeforeClosingBracket.js b/lib/util/getTokenBeforeClosingBracket.js
index a727c1a7b1..8bd277a249 100644
--- a/lib/util/getTokenBeforeClosingBracket.js
+++ b/lib/util/getTokenBeforeClosingBracket.js
@@ -7,7 +7,7 @@
*/
function getTokenBeforeClosingBracket(node) {
const attributes = node.attributes;
- if (attributes.length === 0) {
+ if (!attributes || attributes.length === 0) {
return node.name;
}
return attributes[attributes.length - 1];
diff --git a/tests/lib/rules/jsx-tag-spacing.js b/tests/lib/rules/jsx-tag-spacing.js
index e2a9c759a2..039591a7f5 100644
--- a/tests/lib/rules/jsx-tag-spacing.js
+++ b/tests/lib/rules/jsx-tag-spacing.js
@@ -114,21 +114,13 @@ 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'),
+ options: beforeSelfClosingOptions('proportional-always'),
},
{
code: '',
- options: beforeSelfClosingOptions('multiline-always'),
+ options: beforeSelfClosingOptions('proportional-always'),
},
{
code: `
@@ -139,7 +131,7 @@ ruleTester.run('jsx-tag-spacing', rule, {
hello
`,
- options: beforeClosingOptions('multiline-always'),
+ options: beforeClosingOptions('proportional-always'),
},
{
code: `
@@ -147,7 +139,7 @@ ruleTester.run('jsx-tag-spacing', rule, {
hello
`,
- options: beforeClosingOptions('multiline-always'),
+ options: beforeClosingOptions('proportional-always'),
},
{
code: `
@@ -155,7 +147,7 @@ ruleTester.run('jsx-tag-spacing', rule, {
foo={bar}
/>
`,
- options: beforeSelfClosingOptions('multiline-always'),
+ options: beforeSelfClosingOptions('proportional-always'),
},
{
code: '',
@@ -345,6 +337,18 @@ ruleTester.run('jsx-tag-spacing', rule, {
options: beforeSelfClosingOptions('never'),
errors: [{ messageId: 'beforeSelfCloseNoSpace' }],
},
+ {
+ code: '',
+ output: '',
+ options: beforeSelfClosingOptions('proportional-always'),
+ errors: [{ messageId: 'beforeSelfCloseNeedSpace' }],
+ },
+ {
+ code: '',
+ output: '',
+ options: beforeSelfClosingOptions('proportional-always'),
+ errors: [{ messageId: 'beforeSelfCloseNeedSpace' }],
+ },
{
code: `
`,
- options: beforeSelfClosingOptions('multiline-always'),
+ options: beforeSelfClosingOptions('proportional-always'),
errors: [{ messageId: 'beforeSelfCloseNeedNewline' }],
},
{
@@ -364,7 +368,7 @@ ruleTester.run('jsx-tag-spacing', rule, {
`,
- options: beforeSelfClosingOptions('multiline-always'),
+ options: beforeSelfClosingOptions('proportional-always'),
errors: [{ messageId: 'beforeSelfCloseNeedNewline' }],
},
{
@@ -383,7 +387,7 @@ ruleTester.run('jsx-tag-spacing', rule, {
hello
`,
- options: beforeClosingOptions('multiline-always'),
+ options: beforeClosingOptions('proportional-always'),
errors: [{ messageId: 'beforeCloseNeedNewline' }],
},
{
@@ -400,7 +404,7 @@ ruleTester.run('jsx-tag-spacing', rule, {
hello
`,
- options: beforeClosingOptions('multiline-always'),
+ options: beforeClosingOptions('proportional-always'),
errors: [{ messageId: 'beforeCloseNeedNewline' }],
},
{