Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New] jsx-tag-spacing: Add multiline-always option #3260

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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