Skip to content

Commit cac3838

Browse files
hdupratljharb
hduprat
authored andcommitted
[Fix] jsx-no-leaked-render: autofix nested "&&" logical expressions
1 parent b7f388b commit cac3838

File tree

3 files changed

+64
-9
lines changed

3 files changed

+64
-9
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
2222
* [`forbid-prop-types`]: Ignore objects that are not of type React.PropTypes ([#3326][] @TildaDares)
2323
* [`display-name`], component detection: fix false positive for HOF returning only nulls and literals ([#3305][] @golopot)
2424
* [`jsx-no-target-blank`]: False negative when rel attribute is assigned using ConditionalExpression ([#3332][] @V2dha)
25+
* [`jsx-no-leaked-render`]: autofix nested "&&" logical expressions ([#3353][] @hduprat)
2526

2627
### Changed
2728
* [Refactor] [`jsx-indent-props`]: improved readability of the checkNodesIndent function ([#3315][] @caroline223)
@@ -33,6 +34,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
3334
* [readme] remove dead codeclimate badge, add actions badge (@ljharb)
3435
* [readme] Remove dead david-dm badge ([#3262][] @ddzz)
3536

37+
[#3353]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3353
3638
[#3350]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3350
3739
[#3349]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3349
3840
[#3347]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3347

lib/rules/jsx-no-leaked-render.js

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,30 @@ function getIsCoerceValidNestedLogicalExpression(node) {
4040
return COERCE_VALID_LEFT_SIDE_EXPRESSIONS.some((validExpression) => validExpression === node.type);
4141
}
4242

43+
function extractExpressionBetweenLogicalAnds(node) {
44+
if (node.type !== 'LogicalExpression') return [node];
45+
if (node.operator !== '&&') return [node];
46+
return [].concat(
47+
extractExpressionBetweenLogicalAnds(node.left),
48+
extractExpressionBetweenLogicalAnds(node.right)
49+
);
50+
}
51+
4352
function ruleFixer(context, fixStrategy, fixer, reportedNode, leftNode, rightNode) {
4453
const sourceCode = context.getSourceCode();
4554
const rightSideText = sourceCode.getText(rightNode);
4655

4756
if (fixStrategy === COERCE_STRATEGY) {
48-
let leftSideText = sourceCode.getText(leftNode);
49-
if (isParenthesized(context, leftNode)) {
50-
leftSideText = `(${leftSideText})`;
51-
}
52-
53-
const shouldPrefixDoubleNegation = leftNode.type !== 'UnaryExpression';
54-
55-
return fixer.replaceText(reportedNode, `${shouldPrefixDoubleNegation ? '!!' : ''}${leftSideText} && ${rightSideText}`);
57+
const expressions = extractExpressionBetweenLogicalAnds(leftNode);
58+
const newText = expressions.map((node) => {
59+
let nodeText = sourceCode.getText(node);
60+
if (isParenthesized(context, node)) {
61+
nodeText = `(${nodeText})`;
62+
}
63+
return `${getIsCoerceValidNestedLogicalExpression(node) ? '' : '!!'}${nodeText}`;
64+
}).join(' && ');
65+
66+
return fixer.replaceText(reportedNode, `${newText} && ${rightSideText}`);
5667
}
5768

5869
if (fixStrategy === TERNARY_STRATEGY) {

tests/lib/rules/jsx-no-leaked-render.js

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -725,9 +725,51 @@ ruleTester.run('jsx-no-leaked-render', rule, {
725725
}],
726726
output: `
727727
const Component = ({ count, somethingElse, title }) => {
728-
return <div>{!!count && somethingElse && title}</div>
728+
return <div>{!!count && !!somethingElse && title}</div>
729729
}
730730
`,
731731
},
732+
{
733+
code: `
734+
const Component = ({ items, somethingElse, title }) => {
735+
return <div>{items.length > 0 && somethingElse && title}</div>
736+
}
737+
`,
738+
options: [{ validStrategies: ['coerce'] }],
739+
errors: [{
740+
message: 'Potential leaked value that might cause unintentionally rendered values or rendering crashes',
741+
line: 3,
742+
column: 24,
743+
}],
744+
output: `
745+
const Component = ({ items, somethingElse, title }) => {
746+
return <div>{items.length > 0 && !!somethingElse && title}</div>
747+
}
748+
`,
749+
},
750+
{
751+
code: `
752+
const MyComponent = () => {
753+
const items = []
754+
const breakpoint = { phones: true }
755+
756+
return <div>{items.length > 0 && breakpoint.phones && <span />}</div>
757+
}
758+
`,
759+
options: [{ validStrategies: ['coerce', 'ternary'] }],
760+
output: `
761+
const MyComponent = () => {
762+
const items = []
763+
const breakpoint = { phones: true }
764+
765+
return <div>{items.length > 0 && !!breakpoint.phones && <span />}</div>
766+
}
767+
`,
768+
errors: [{
769+
message: 'Potential leaked value that might cause unintentionally rendered values or rendering crashes',
770+
line: 6,
771+
column: 24,
772+
}],
773+
},
732774
]),
733775
});

0 commit comments

Comments
 (0)