Skip to content

Commit

Permalink
[New] jsx-newline: Add prevent option
Browse files Browse the repository at this point in the history
Fixes #2793. Closes #2927.
  • Loading branch information
Joe Stiles authored and ljharb committed Feb 27, 2021
1 parent bf8dff0 commit a686079
Show file tree
Hide file tree
Showing 5 changed files with 368 additions and 38 deletions.
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

0 comments on commit a686079

Please sign in to comment.