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-newline support prevent option #2935

Merged
merged 1 commit into from Mar 23, 2021
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 @@ -9,6 +9,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
* [`jsx-no-target-blank`]: add fixer ([#2862][] @Nokel81)
* [`jsx-pascal-case`]: support minimatch `ignore` option ([#2906][] @bcherny)
* [`jsx-pascal-case`]: support `allowNamespace` option ([#2917][] @kev-y-huang)
* [`jsx-newline`]: Add prevent option ([#2935][] @jsphstls)

### Fixed
* [`jsx-no-constructed-context-values`]: avoid a crash with `as X` TS code ([#2894][] @ljharb)
Expand All @@ -28,6 +29,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
* [Docs] added missing curly braces ([#2923][] @Muditxofficial)

[#2943]: https://github.com/yannickcr/eslint-plugin-react/pull/2943
[#2935]: https://github.com/yannickcr/eslint-plugin-react/pull/2935
[#2930]: https://github.com/yannickcr/eslint-plugin-react/pull/2930
[#2929]: https://github.com/yannickcr/eslint-plugin-react/pull/2929
[#2925]: https://github.com/yannickcr/eslint-plugin-react/pull/2925
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -179,7 +179,7 @@ Enable the rules that you would like to use.
| ✔ | | [react/jsx-key](docs/rules/jsx-key.md) | Report missing `key` props in iterators/collection literals |
| | | [react/jsx-max-depth](docs/rules/jsx-max-depth.md) | Validate JSX maximum depth |
| | 🔧 | [react/jsx-max-props-per-line](docs/rules/jsx-max-props-per-line.md) | Limit maximum of props on a single line in JSX |
| | 🔧 | [react/jsx-newline](docs/rules/jsx-newline.md) | Enforce a new line after jsx elements and expressions |
| | 🔧 | [react/jsx-newline](docs/rules/jsx-newline.md) | Require or prevent a new line after jsx elements and expressions |
| | | [react/jsx-no-bind](docs/rules/jsx-no-bind.md) | Prevents usage of Function.prototype.bind and arrow functions in React component props |
| ✔ | | [react/jsx-no-comment-textnodes](docs/rules/jsx-no-comment-textnodes.md) | Comments inside children section of tag should be placed inside braces |
| | | [react/jsx-no-constructed-context-values](docs/rules/jsx-no-constructed-context-values.md) | Prevents JSX context provider values from taking values that will cause needless rerenders. |
Expand Down
75 changes: 71 additions & 4 deletions docs/rules/jsx-newline.md
@@ -1,12 +1,24 @@
# Enforce a new line after jsx elements and expressions (react/jsx-newline)
# Require or prevent a new line after jsx elements and expressions. (react/jsx-newline)

**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line.

## Rule Details

This is a stylistic rule intended to make JSX code more readable by enforcing spaces between adjacent JSX elements and expressions.
This is a stylistic rule intended to make JSX code more readable by requiring or preventing lines between adjacent JSX elements and expressions.

Examples of **incorrect** code for this rule:
## Rule Options
```json
...
"react/jsx-new-line": [<enabled>, { "prevent": <boolean> }]
...
```

* enabled: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0.
* prevent: optional boolean. If `true` prevents empty lines between adjacent JSX elements and expressions. Defaults to `false`.

## Examples

Examples of **incorrect** code for this rule, when configured with `{ "prevent": false }`:

```jsx
<div>
Expand All @@ -33,7 +45,7 @@ Examples of **incorrect** code for this rule:
</div>
```

Examples of **correct** code for this rule:
Examples of **correct** code for this rule, when configured with `{ "prevent": false }`:

```jsx
<div>
Expand All @@ -60,6 +72,61 @@ Examples of **correct** code for this rule:
</div>
```

Examples of **incorrect** code for this rule, when configured with `{ "prevent": true }`:


```jsx
<div>
<Button>{data.label}</Button>

<List />

<Button>
<IconPreview />
Button 2

<span></span>
</Button>

{showSomething === true && <Something />}

<Button>Button 3</Button>

{showSomethingElse === true ? (
<SomethingElse />
) : (
<ErrorMessage />
)}
</div>
```

Examples of **correct** code for this rule, when configured with `{ "prevent": true }`:

```jsx
<div>
<Button>{data.label}</Button>
<List />
</div>
```

```jsx
<div>
<Button>{data.label}</Button>
{showSomething === true && <Something />}
</div>
```

```jsx
<div>
{showSomething === true && <Something />}
{showSomethingElse === true ? (
<SomethingElse />
) : (
<ErrorMessage />
)}
</div>
```

## When Not To Use It

You can turn this rule off if you are not concerned with spacing between your JSX elements and expressions.
80 changes: 56 additions & 24 deletions lib/rules/jsx-newline.js
@@ -1,6 +1,7 @@
/**
* @fileoverview Enforce a new line after jsx elements and expressions.
* @fileoverview Require or prevent a new line after jsx elements and expressions.
* @author Johnny Zabala
* @author Joseph Stiles
*/

'use strict';
Expand All @@ -14,16 +15,29 @@ const docsUrl = require('../util/docsUrl');
module.exports = {
meta: {
docs: {
description: 'Enforce a new line after jsx elements and expressions',
description: 'Require or prevent a new line after jsx elements and expressions.',
category: 'Stylistic Issues',
recommended: false,
url: docsUrl('jsx-newline')
},
fixable: 'code',

messages: {
newLine: 'JSX element should start in a new line'
}
require: 'JSX element should start in a new line',
prevent: 'JSX element should not start in a new line'
},
schema: [
{
type: 'object',
properties: {
prevent: {
default: false,
type: 'boolean'
}
},
additionalProperties: false
}
]
},
create(context) {
const jsxElementParents = new Set();
Expand All @@ -35,26 +49,44 @@ module.exports = {
if (element.type === 'JSXElement' || element.type === 'JSXExpressionContainer') {
const firstAdjacentSibling = elements[index + 1];
const secondAdjacentSibling = elements[index + 2];
if (
firstAdjacentSibling
&& secondAdjacentSibling
&& (firstAdjacentSibling.type === 'Literal' || firstAdjacentSibling.type === 'JSXText')
// Check adjacent sibling has the proper amount of newlines
&& !/\n\s*\n/.test(firstAdjacentSibling.value)
) {
context.report({
node: secondAdjacentSibling,
messageId: 'newLine',
fix(fixer) {
return fixer.replaceText(
firstAdjacentSibling,
// double the last newline.
sourceCode.getText(firstAdjacentSibling)
.replace(/(\n)(?!.*\1)/g, '\n\n')
);
}
});
}

const hasSibling = firstAdjacentSibling
&& secondAdjacentSibling
&& (firstAdjacentSibling.type === 'Literal' || firstAdjacentSibling.type === 'JSXText');

if (!hasSibling) return;

// Check adjacent sibling has the proper amount of newlines
const isWithoutNewLine = !/\n\s*\n/.test(firstAdjacentSibling.value);

const prevent = !!(context.options[0] || {}).prevent;

if (isWithoutNewLine === prevent) return;

const messageId = prevent
? 'prevent'
: 'require';

const regex = prevent
? /(\n\n)(?!.*\1)/g
: /(\n)(?!.*\1)/g;

const replacement = prevent
? '\n'
: '\n\n';

context.report({
node: secondAdjacentSibling,
messageId,
fix(fixer) {
return fixer.replaceText(
firstAdjacentSibling,
// double or remove the last newline
sourceCode.getText(firstAdjacentSibling)
.replace(regex, replacement)
);
}
});
}
});
});
Expand Down