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: '',