Skip to content

Commit 8513f1a

Browse files
Nokel81ljharb
authored andcommitted
[New] jsx-tag-spacing: Add multiline-always option
- This option enforces that the closingTag or the selfClosingTag is on a new line if the entire tag is a multiline tag Signed-off-by: Sebastian Malton <[email protected]>
1 parent 04da3f0 commit 8513f1a

File tree

4 files changed

+178
-4
lines changed

4 files changed

+178
-4
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
88
### Added
99
* [`destructuring-assignment`]: add option `destructureInSignature` ([#3235][] @golopot)
1010
* [`no-unknown-property`]: Allow crossOrigin on image tag (SVG) ([#3251][] @zpao)
11+
* [`jsx-tag-spacing`]: Add `multiline-always` option ([#3260][] @Nokel81)
1112

1213
### Fixed
1314
* [`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
1819
* [Refactor] fix linter errors ([#3261][] @golopot)
1920

2021
[#3261]: https://github.com/yannickcr/eslint-plugin-react/pull/3261
22+
[#3260]: https://github.com/yannickcr/eslint-plugin-react/pull/3260
2123
[#3254]: https://github.com/yannickcr/eslint-plugin-react/pull/3254
2224
[#3251]: https://github.com/yannickcr/eslint-plugin-react/pull/3251
2325
[#3244]: https://github.com/yannickcr/eslint-plugin-react/pull/3244

docs/rules/jsx-tag-spacing.md

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ Examples of **correct** code for this rule, when configured with `{ "closingSlas
6262

6363
### `beforeSelfClosing`
6464

65-
This check can be set to `"always"`, `"never"` or `"allow"` (to disable it).
65+
This check can be set to `"always"`, `"never"`, `"multiline-always"`, or `"allow"` (to disable it).
6666

6767
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"`.
6868

@@ -102,6 +102,26 @@ Examples of **correct** code for this rule, when configured with `{ "beforeSelfC
102102
/>
103103
```
104104

105+
Examples of **incorrect** code for this rule, when configured with `{ "beforeSelfClosing": "multiline-always" }`:
106+
107+
```jsx
108+
<Hello
109+
firstName="John"
110+
lastName="Smith" />
111+
<Hello
112+
firstName="John"
113+
lastName="Smith"/>
114+
```
115+
116+
Examples of **correct** code for this rule, when configured with `{ "beforeSelfClosing": "multiline-always" }`:
117+
118+
```jsx
119+
<Hello
120+
firstName="John"
121+
lastName="Smith"
122+
/>
123+
```
124+
105125
### `afterOpening`
106126

107127
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
179199

180200
### `beforeClosing`
181201

182-
This check can be set to `"always"`, `"never"`, or `"allow"` (to disable it).
202+
This check can be set to `"always"`, `"never"`, `"multiline-always"`, or `"allow"` (to disable it).
183203

184204
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"`.
185205

@@ -219,6 +239,31 @@ Examples of **correct** code for this rule, when configured with `{ "beforeClosi
219239
</Hello>
220240
```
221241

242+
Examples of **incorrect** code for this rule, when configured with `{ "beforeClosing": "multiline-always" }`:
243+
244+
```jsx
245+
<Hello
246+
firstName="John"
247+
lastName="Smith">
248+
</Hello>
249+
<Hello
250+
firstName="John"
251+
lastName="Smith" >
252+
Goodbye
253+
</Hello>
254+
```
255+
256+
Examples of **correct** code for this rule, when configured with `{ "beforeClosing": "multiline-always" }`:
257+
258+
```jsx
259+
<Hello
260+
firstName="John"
261+
lastName="Smith"
262+
>
263+
Goodbye
264+
</Hello>
265+
```
266+
222267
## When Not To Use It
223268

224269
You can turn this rule off if you are not concerned with the consistency of spacing in or around JSX brackets.

lib/rules/jsx-tag-spacing.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ const messages = {
1616
closeSlashNeedSpace: 'Whitespace is required between `<` and `/`; write `< /`',
1717
beforeSelfCloseNoSpace: 'A space is forbidden before closing bracket',
1818
beforeSelfCloseNeedSpace: 'A space is required before closing bracket',
19+
beforeSelfCloseNeedNewline: 'A newline is required before closing bracket',
1920
afterOpenNoSpace: 'A space is forbidden after opening bracket',
2021
afterOpenNeedSpace: 'A space is required after opening bracket',
2122
beforeCloseNoSpace: 'A space is forbidden before closing bracket',
2223
beforeCloseNeedSpace: 'Whitespace is required before closing bracket',
24+
beforeCloseNeedNewline: 'A newline is required before closing bracket',
2325
};
2426

2527
// ------------------------------------------------------------------------------
@@ -99,6 +101,18 @@ function validateBeforeSelfClosing(context, node, option) {
99101
const leftToken = getTokenBeforeClosingBracket(node);
100102
const closingSlash = sourceCode.getTokenAfter(leftToken);
101103

104+
if (node.loc.start.line !== node.loc.end.line && option === 'multiline-always') {
105+
if (leftToken.loc.end.line === closingSlash.loc.start.line) {
106+
report(context, messages.beforeSelfCloseNeedNewline, 'beforeSelfCloseNeedNewline', {
107+
node,
108+
loc: leftToken.loc.end,
109+
fix(fixer) {
110+
return fixer.insertTextBefore(closingSlash, '\n');
111+
},
112+
});
113+
}
114+
}
115+
102116
if (leftToken.loc.end.line !== closingSlash.loc.start.line) {
103117
return;
104118
}
@@ -170,6 +184,18 @@ function validateBeforeClosing(context, node, option) {
170184
const closingToken = lastTokens[1];
171185
const leftToken = lastTokens[0];
172186

187+
if (node.loc.start.line !== node.loc.end.line && option === 'multiline-always') {
188+
if (leftToken.loc.end.line === closingToken.loc.start.line) {
189+
report(context, messages.beforeCloseNeedNewline, 'beforeCloseNeedNewline', {
190+
node,
191+
loc: leftToken.loc.end,
192+
fix(fixer) {
193+
return fixer.insertTextBefore(closingToken, '\n');
194+
},
195+
});
196+
}
197+
}
198+
173199
if (leftToken.loc.start.line !== closingToken.loc.start.line) {
174200
return;
175201
}
@@ -233,13 +259,13 @@ module.exports = {
233259
enum: ['always', 'never', 'allow'],
234260
},
235261
beforeSelfClosing: {
236-
enum: ['always', 'never', 'allow'],
262+
enum: ['always', 'multiline-always', 'never', 'allow'],
237263
},
238264
afterOpening: {
239265
enum: ['always', 'allow-multiline', 'never', 'allow'],
240266
},
241267
beforeClosing: {
242-
enum: ['always', 'never', 'allow'],
268+
enum: ['always', 'multiline-always', 'never', 'allow'],
243269
},
244270
},
245271
default: optionDefaults,

tests/lib/rules/jsx-tag-spacing.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,49 @@ ruleTester.run('jsx-tag-spacing', rule, {
114114
code: '<App/>',
115115
options: beforeSelfClosingOptions('never'),
116116
},
117+
{
118+
code: '<App/>',
119+
options: beforeSelfClosingOptions('multiline-always'),
120+
},
121+
{
122+
code: '<App />',
123+
options: beforeSelfClosingOptions('multiline-always'),
124+
},
125+
{
126+
code: '<App foo/>',
127+
options: beforeSelfClosingOptions('multiline-always'),
128+
},
129+
{
130+
code: '<App foo />',
131+
options: beforeSelfClosingOptions('multiline-always'),
132+
},
133+
{
134+
code: `
135+
<App
136+
foo={bar}
137+
blat
138+
>
139+
hello
140+
</App>
141+
`,
142+
options: beforeClosingOptions('multiline-always'),
143+
},
144+
{
145+
code: `
146+
<App foo={bar}>
147+
hello
148+
</App>
149+
`,
150+
options: beforeClosingOptions('multiline-always'),
151+
},
152+
{
153+
code: `
154+
<App
155+
foo={bar}
156+
/>
157+
`,
158+
options: beforeSelfClosingOptions('multiline-always'),
159+
},
117160
{
118161
code: '<App foo/>',
119162
options: beforeSelfClosingOptions('never'),
@@ -302,6 +345,64 @@ ruleTester.run('jsx-tag-spacing', rule, {
302345
options: beforeSelfClosingOptions('never'),
303346
errors: [{ messageId: 'beforeSelfCloseNoSpace' }],
304347
},
348+
{
349+
code: `
350+
<App
351+
foo={bar}/>`,
352+
output: `
353+
<App
354+
foo={bar}
355+
/>`,
356+
options: beforeSelfClosingOptions('multiline-always'),
357+
errors: [{ messageId: 'beforeSelfCloseNeedNewline' }],
358+
},
359+
{
360+
code: `
361+
<App
362+
foo={bar} />`,
363+
output: `
364+
<App
365+
foo={bar}${' '}
366+
/>`,
367+
options: beforeSelfClosingOptions('multiline-always'),
368+
errors: [{ messageId: 'beforeSelfCloseNeedNewline' }],
369+
},
370+
{
371+
code: `
372+
<App
373+
foo={bar}
374+
blat >
375+
hello
376+
</App>
377+
`,
378+
output: `
379+
<App
380+
foo={bar}
381+
blat${' '}
382+
>
383+
hello
384+
</App>
385+
`,
386+
options: beforeClosingOptions('multiline-always'),
387+
errors: [{ messageId: 'beforeCloseNeedNewline' }],
388+
},
389+
{
390+
code: `
391+
<App
392+
foo={bar}>
393+
hello
394+
</App>
395+
`,
396+
output: `
397+
<App
398+
foo={bar}
399+
>
400+
hello
401+
</App>
402+
`,
403+
options: beforeClosingOptions('multiline-always'),
404+
errors: [{ messageId: 'beforeCloseNeedNewline' }],
405+
},
305406
{
306407
code: '<App {...props} />',
307408
output: '<App {...props}/>',

0 commit comments

Comments
 (0)