Skip to content

Commit

Permalink
[New] jsx-tag-spacing: Add multiline-always option
Browse files Browse the repository at this point in the history
- 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 <sebastian@malton.name>
  • Loading branch information
Nokel81 authored and ljharb committed Apr 1, 2022
1 parent 04da3f0 commit 8513f1a
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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)
Expand All @@ -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
Expand Down
49 changes: 47 additions & 2 deletions docs/rules/jsx-tag-spacing.md
Expand Up @@ -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"`.

Expand Down Expand Up @@ -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
<Hello
firstName="John"
lastName="Smith" />
<Hello
firstName="John"
lastName="Smith"/>
```

Examples of **correct** code for this rule, when configured with `{ "beforeSelfClosing": "multiline-always" }`:

```jsx
<Hello
firstName="John"
lastName="Smith"
/>
```

### `afterOpening`

This check can be set to `"always"`, `"never"`, `"allow-multiline"` or `"allow"` (to disable it).
Expand Down Expand Up @@ -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"`.

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

Examples of **incorrect** code for this rule, when configured with `{ "beforeClosing": "multiline-always" }`:

```jsx
<Hello
firstName="John"
lastName="Smith">
</Hello>
<Hello
firstName="John"
lastName="Smith" >
Goodbye
</Hello>
```

Examples of **correct** code for this rule, when configured with `{ "beforeClosing": "multiline-always" }`:

```jsx
<Hello
firstName="John"
lastName="Smith"
>
Goodbye
</Hello>
```

## 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.
30 changes: 28 additions & 2 deletions lib/rules/jsx-tag-spacing.js
Expand Up @@ -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',
};

// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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,
Expand Down
101 changes: 101 additions & 0 deletions tests/lib/rules/jsx-tag-spacing.js
Expand Up @@ -114,6 +114,49 @@ ruleTester.run('jsx-tag-spacing', rule, {
code: '<App/>',
options: beforeSelfClosingOptions('never'),
},
{
code: '<App/>',
options: beforeSelfClosingOptions('multiline-always'),
},
{
code: '<App />',
options: beforeSelfClosingOptions('multiline-always'),
},
{
code: '<App foo/>',
options: beforeSelfClosingOptions('multiline-always'),
},
{
code: '<App foo />',
options: beforeSelfClosingOptions('multiline-always'),
},
{
code: `
<App
foo={bar}
blat
>
hello
</App>
`,
options: beforeClosingOptions('multiline-always'),
},
{
code: `
<App foo={bar}>
hello
</App>
`,
options: beforeClosingOptions('multiline-always'),
},
{
code: `
<App
foo={bar}
/>
`,
options: beforeSelfClosingOptions('multiline-always'),
},
{
code: '<App foo/>',
options: beforeSelfClosingOptions('never'),
Expand Down Expand Up @@ -302,6 +345,64 @@ ruleTester.run('jsx-tag-spacing', rule, {
options: beforeSelfClosingOptions('never'),
errors: [{ messageId: 'beforeSelfCloseNoSpace' }],
},
{
code: `
<App
foo={bar}/>`,
output: `
<App
foo={bar}
/>`,
options: beforeSelfClosingOptions('multiline-always'),
errors: [{ messageId: 'beforeSelfCloseNeedNewline' }],
},
{
code: `
<App
foo={bar} />`,
output: `
<App
foo={bar}${' '}
/>`,
options: beforeSelfClosingOptions('multiline-always'),
errors: [{ messageId: 'beforeSelfCloseNeedNewline' }],
},
{
code: `
<App
foo={bar}
blat >
hello
</App>
`,
output: `
<App
foo={bar}
blat${' '}
>
hello
</App>
`,
options: beforeClosingOptions('multiline-always'),
errors: [{ messageId: 'beforeCloseNeedNewline' }],
},
{
code: `
<App
foo={bar}>
hello
</App>
`,
output: `
<App
foo={bar}
>
hello
</App>
`,
options: beforeClosingOptions('multiline-always'),
errors: [{ messageId: 'beforeCloseNeedNewline' }],
},
{
code: '<App {...props} />',
output: '<App {...props}/>',
Expand Down

0 comments on commit 8513f1a

Please sign in to comment.