Skip to content

Commit 757f0b5

Browse files
authored
Merge branch 'master' into jsx-key-bug
2 parents c0aa40e + 7a7bb99 commit 757f0b5

File tree

8 files changed

+310
-31
lines changed

8 files changed

+310
-31
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
55

66
## Unreleased
77

8+
### Added
9+
* [`jsx-newline`]: add `allowMultiline` option when prevent option is true ([#3311][] @TildaDares)
10+
11+
### Fixed
12+
* [`jsx-no-literals`]: properly error on children with noAttributeStrings: true ([#3317][] @TildaDares)
13+
14+
### Changed
15+
* [Refactor] [`jsx-indent-props`]: improved readability of the checkNodesIndent function ([#3315][] @caroline223)
16+
17+
[#3317]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3317
18+
[#3315]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3315
19+
[#3311]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3311
20+
21+
## [7.30.1] - 2022.06.23
22+
823
### Fixed
924
* [`display-name`]: fix false positive for HOF returning only nulls ([#3291][] @golopot)
1025
* [`jsx-no-leaked-render`]: avoid unnecessary negation operators and ternary branches deletion ([#3299][] @Belco90)
@@ -14,6 +29,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
1429
* [Docs] [`jsx-tag-spacing`]: rename option from [#3264][] ([#3294[] @ljharb)
1530
* [Docs] [`jsx-key`]: split the examples ([#3293][] @ioggstream)
1631

32+
[7.30.1]: https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.30.0...v7.30.1
1733
[#3304]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3304
1834
[#3299]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3299
1935
[#3294]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3294
@@ -29,6 +45,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
2945
* [`function-component-definition`]: replace `var` by `const` in certain situations ([#3248][] @JohnBerd @SimeonC)
3046
* add [`jsx-no-leaked-render`] ([#3203][] @Belco90)
3147
* [`require-default-props`]: add option `functions` ([#3249][] @nix6839)
48+
* [`jsx-newline`]: Add `allowMultilines` option ([#3311][] @TildaDares)
3249

3350
### Fixed
3451
* [`hook-use-state`]: Allow UPPERCASE setState setter prefixes ([#3244][] @duncanbeevers)

docs/rules/jsx-newline.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ This is a stylistic rule intended to make JSX code more readable by requiring or
99
## Rule Options
1010
```json
1111
...
12-
"react/jsx-newline": [<enabled>, { "prevent": <boolean> }]
12+
"react/jsx-newline": [<enabled>, { "prevent": <boolean>, "allowMultilines": <boolean> }]
1313
...
1414
```
1515

1616
* enabled: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0.
1717
* prevent: optional boolean. If `true` prevents empty lines between adjacent JSX elements and expressions. Defaults to `false`.
18+
* allowMultilines: optional boolean. If `true` and `prevent` is also equal to `true`, it allows newlines after multiline JSX elements and expressions. Defaults to `false`.
1819

1920
## Examples
2021

@@ -127,6 +128,37 @@ Examples of **correct** code for this rule, when configured with `{ "prevent": t
127128
</div>
128129
```
129130

131+
Examples of **incorrect** code for this rule, when configured with `{ "prevent": true, "allowMultilines": true }`:
132+
133+
```jsx
134+
<div>
135+
{showSomething === true && <Something />}
136+
137+
<Button>Button 3</Button>
138+
{showSomethingElse === true ? (
139+
<SomethingElse />
140+
) : (
141+
<ErrorMessage />
142+
)}
143+
</div>
144+
```
145+
146+
Examples of **correct** code for this rule, when configured with `{ "prevent": true, "allowMultilines": true }`:
147+
148+
```jsx
149+
<div>
150+
{showSomething === true && <Something />}
151+
152+
<Button>Button 3</Button>
153+
154+
{showSomethingElse === true ? (
155+
<SomethingElse />
156+
) : (
157+
<ErrorMessage />
158+
)}
159+
</div>
160+
```
161+
130162
## When Not To Use It
131163

132164
You can turn this rule off if you are not concerned with spacing between your JSX elements and expressions.

lib/rules/jsx-indent-props.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,17 +172,23 @@ module.exports = {
172172
* @param {Number} indent needed indent
173173
*/
174174
function checkNodesIndent(nodes, indent) {
175+
let nestedIndent = indent;
175176
nodes.forEach((node) => {
176177
const nodeIndent = getNodeIndent(node);
177-
if (line.isUsingOperator && !line.currentOperator && indentSize !== 'first' && !ignoreTernaryOperator) {
178-
indent += indentSize;
178+
if (
179+
line.isUsingOperator
180+
&& !line.currentOperator
181+
&& indentSize !== 'first'
182+
&& !ignoreTernaryOperator
183+
) {
184+
nestedIndent += indentSize;
179185
line.isUsingOperator = false;
180186
}
181187
if (
182188
node.type !== 'ArrayExpression' && node.type !== 'ObjectExpression'
183-
&& nodeIndent !== indent && astUtil.isNodeFirstInLine(context, node)
189+
&& nodeIndent !== nestedIndent && astUtil.isNodeFirstInLine(context, node)
184190
) {
185-
report(node, indent, nodeIndent);
191+
report(node, nestedIndent, nodeIndent);
186192
}
187193
});
188194
}

lib/rules/jsx-newline.js

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@ const report = require('../util/report');
1616
const messages = {
1717
require: 'JSX element should start in a new line',
1818
prevent: 'JSX element should not start in a new line',
19+
allowMultilines: 'Multiline JSX elements should start in a new line',
1920
};
2021

22+
function isMultilined(node) {
23+
return node.loc.start.line !== node.loc.end.line;
24+
}
25+
2126
module.exports = {
2227
meta: {
2328
docs: {
@@ -37,19 +42,45 @@ module.exports = {
3742
default: false,
3843
type: 'boolean',
3944
},
45+
allowMultilines: {
46+
default: false,
47+
type: 'boolean',
48+
},
4049
},
4150
additionalProperties: false,
51+
if: {
52+
properties: {
53+
allowMultilines: {
54+
const: true,
55+
},
56+
},
57+
},
58+
then: {
59+
properties: {
60+
prevent: {
61+
const: true,
62+
},
63+
},
64+
required: [
65+
'prevent',
66+
],
67+
},
4268
},
4369
],
4470
},
4571
create(context) {
4672
const jsxElementParents = new Set();
4773
const sourceCode = context.getSourceCode();
74+
4875
return {
4976
'Program:exit'() {
5077
jsxElementParents.forEach((parent) => {
5178
parent.children.forEach((element, index, elements) => {
5279
if (element.type === 'JSXElement' || element.type === 'JSXExpressionContainer') {
80+
const configuration = context.options[0] || {};
81+
const prevent = configuration.prevent || false;
82+
const allowMultilines = configuration.allowMultilines || false;
83+
5384
const firstAdjacentSibling = elements[index + 1];
5485
const secondAdjacentSibling = elements[index + 2];
5586

@@ -62,10 +93,28 @@ module.exports = {
6293
// Check adjacent sibling has the proper amount of newlines
6394
const isWithoutNewLine = !/\n\s*\n/.test(firstAdjacentSibling.value);
6495

65-
const prevent = !!(context.options[0] || {}).prevent;
96+
if (allowMultilines && (isMultilined(element) || isMultilined(secondAdjacentSibling))) {
97+
if (!isWithoutNewLine) return;
6698

67-
if (isWithoutNewLine === prevent) return;
99+
const regex = /(\n)(?!.*\1)/g;
100+
const replacement = '\n\n';
101+
const messageId = 'allowMultilines';
68102

103+
report(context, messages[messageId], messageId, {
104+
node: secondAdjacentSibling,
105+
fix(fixer) {
106+
return fixer.replaceText(
107+
firstAdjacentSibling,
108+
sourceCode.getText(firstAdjacentSibling)
109+
.replace(regex, replacement)
110+
);
111+
},
112+
});
113+
114+
return;
115+
}
116+
117+
if (isWithoutNewLine === prevent) return;
69118
const messageId = prevent
70119
? 'prevent'
71120
: 'require';

lib/rules/jsx-no-literals.js

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ module.exports = {
7070
config.allowedStrings = new Set(config.allowedStrings.map(trimIfString));
7171

7272
function defaultMessageId() {
73-
if (config.noAttributeStrings) {
73+
const ancestorIsJSXElement = arguments.length >= 1 && arguments[0];
74+
if (config.noAttributeStrings && !ancestorIsJSXElement) {
7475
return 'noStringsInAttributes';
7576
}
7677
if (config.noStrings) {
@@ -79,17 +80,6 @@ module.exports = {
7980
return 'literalNotInJSXExpression';
8081
}
8182

82-
function reportLiteralNode(node, messageId) {
83-
messageId = messageId || defaultMessageId();
84-
85-
report(context, messages[messageId], messageId, {
86-
node,
87-
data: {
88-
text: context.getSourceCode().getText(node).trim(),
89-
},
90-
});
91-
}
92-
9383
function getParentIgnoringBinaryExpressions(node) {
9484
let current = node;
9585
while (current.parent.type === 'BinaryExpression') {
@@ -107,7 +97,7 @@ module.exports = {
10797
function isParentNodeStandard() {
10898
if (!/^[\s]+$/.test(node.value) && typeof node.value === 'string' && parent.type.includes('JSX')) {
10999
if (config.noAttributeStrings) {
110-
return parent.type === 'JSXAttribute';
100+
return parent.type === 'JSXAttribute' || parent.type === 'JSXElement';
111101
}
112102
if (!config.noAttributeStrings) {
113103
return parent.type !== 'JSXAttribute';
@@ -146,6 +136,18 @@ module.exports = {
146136
return parentType === 'JSXFragment' || parentType === 'JSXElement' || grandParentType === 'JSXElement';
147137
}
148138

139+
function reportLiteralNode(node, messageId) {
140+
const ancestorIsJSXElement = hasJSXElementParentOrGrandParent(node);
141+
messageId = messageId || defaultMessageId(ancestorIsJSXElement);
142+
143+
report(context, messages[messageId], messageId, {
144+
node,
145+
data: {
146+
text: context.getSourceCode().getText(node).trim(),
147+
},
148+
});
149+
}
150+
149151
// --------------------------------------------------------------------------
150152
// Public
151153
// --------------------------------------------------------------------------

package.json

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "eslint-plugin-react",
3-
"version": "7.30.0",
3+
"version": "7.30.1",
44
"author": "Yannick Croissant <yannick.croissant+npm@gmail.com>",
55
"description": "React specific linting rules for ESLint",
66
"main": "index.js",
@@ -39,14 +39,15 @@
3939
"string.prototype.matchall": "^4.0.7"
4040
},
4141
"devDependencies": {
42-
"@babel/core": "^7.17.12",
43-
"@babel/eslint-parser": "^7.17.0",
44-
"@babel/plugin-syntax-decorators": "^7.17.12",
45-
"@babel/plugin-syntax-do-expressions": "^7.16.7",
46-
"@babel/plugin-syntax-function-bind": "^7.16.7",
47-
"@babel/preset-react": "^7.17.12",
42+
"@babel/core": "^7.18.6",
43+
"@babel/eslint-parser": "^7.18.2",
44+
"@babel/plugin-syntax-decorators": "^7.18.6",
45+
"@babel/plugin-syntax-do-expressions": "^7.18.6",
46+
"@babel/plugin-syntax-function-bind": "^7.18.6",
47+
"@babel/preset-react": "^7.18.6",
48+
"@technote-space/doctoc": "~2.4",
4849
"@types/eslint": "=7.2.10",
49-
"@types/estree": "0.0.51",
50+
"@types/estree": "0.0.52",
5051
"@types/node": "^16.11.35",
5152
"@typescript-eslint/parser": "^2.34.0 || ^3.10.1 || ^4.0.0 || ^5.0.0",
5253
"aud": "^2.0.0",
@@ -55,12 +56,12 @@
5556
"eslint-config-airbnb-base": "^15.0.0",
5657
"eslint-plugin-eslint-plugin": "^2.3.0 || ^3.5.3 || ^4.0.1",
5758
"eslint-plugin-import": "^2.26.0",
58-
"eslint-remote-tester": "^2.1.4",
59-
"eslint-remote-tester-repositories": "^0.0.5",
59+
"eslint-remote-tester": "^3.0.0",
60+
"eslint-remote-tester-repositories": "^0.0.6",
6061
"eslint-scope": "^3.7.3",
6162
"espree": "^3.5.4",
6263
"istanbul": "^0.4.5",
63-
"ls-engines": "^0.6.6",
64+
"ls-engines": "^0.7.0",
6465
"markdown-magic": "^2.6.0",
6566
"mocha": "^5.2.0",
6667
"npmignore": "^0.3.0",

0 commit comments

Comments
 (0)