Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
* [`forbid-prop-types`]: Ignore objects that are not of type React.PropTypes ([#3326][] @TildaDares)
* [`display-name`], component detection: fix false positive for HOF returning only nulls and literals ([#3305][] @golopot)
* [`jsx-no-target-blank`]: False negative when rel attribute is assigned using ConditionalExpression ([#3332][] @V2dha)
* [`jsx-no-leaked-render`]: autofix nested "&&" logical expressions ([#3353][] @hduprat)

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

[#3353]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3353
[#3350]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3350
[#3349]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3349
[#3347]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3347
Expand Down
27 changes: 19 additions & 8 deletions lib/rules/jsx-no-leaked-render.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,30 @@ function getIsCoerceValidNestedLogicalExpression(node) {
return COERCE_VALID_LEFT_SIDE_EXPRESSIONS.some((validExpression) => validExpression === node.type);
}

function extractExpressionBetweenLogicalAnds(node) {
if (node.type !== 'LogicalExpression') return [node];
if (node.operator !== '&&') return [node];
return [].concat(
extractExpressionBetweenLogicalAnds(node.left),
extractExpressionBetweenLogicalAnds(node.right)
);
}

function ruleFixer(context, fixStrategy, fixer, reportedNode, leftNode, rightNode) {
const sourceCode = context.getSourceCode();
const rightSideText = sourceCode.getText(rightNode);

if (fixStrategy === COERCE_STRATEGY) {
let leftSideText = sourceCode.getText(leftNode);
if (isParenthesized(context, leftNode)) {
leftSideText = `(${leftSideText})`;
}

const shouldPrefixDoubleNegation = leftNode.type !== 'UnaryExpression';

return fixer.replaceText(reportedNode, `${shouldPrefixDoubleNegation ? '!!' : ''}${leftSideText} && ${rightSideText}`);
const expressions = extractExpressionBetweenLogicalAnds(leftNode);
const newText = expressions.map((node) => {
let nodeText = sourceCode.getText(node);
if (isParenthesized(context, node)) {
nodeText = `(${nodeText})`;
}
return `${getIsCoerceValidNestedLogicalExpression(node) ? '' : '!!'}${nodeText}`;
}).join(' && ');

return fixer.replaceText(reportedNode, `${newText} && ${rightSideText}`);
}

if (fixStrategy === TERNARY_STRATEGY) {
Expand Down
44 changes: 43 additions & 1 deletion tests/lib/rules/jsx-no-leaked-render.js
Original file line number Diff line number Diff line change
Expand Up @@ -725,9 +725,51 @@ ruleTester.run('jsx-no-leaked-render', rule, {
}],
output: `
const Component = ({ count, somethingElse, title }) => {
return <div>{!!count && somethingElse && title}</div>
return <div>{!!count && !!somethingElse && title}</div>
}
`,
},
{
code: `
const Component = ({ items, somethingElse, title }) => {
return <div>{items.length > 0 && somethingElse && title}</div>
}
`,
options: [{ validStrategies: ['coerce'] }],
errors: [{
message: 'Potential leaked value that might cause unintentionally rendered values or rendering crashes',
line: 3,
column: 24,
}],
output: `
const Component = ({ items, somethingElse, title }) => {
return <div>{items.length > 0 && !!somethingElse && title}</div>
}
`,
},
{
code: `
const MyComponent = () => {
const items = []
const breakpoint = { phones: true }

return <div>{items.length > 0 && breakpoint.phones && <span />}</div>
}
`,
options: [{ validStrategies: ['coerce', 'ternary'] }],
output: `
const MyComponent = () => {
const items = []
const breakpoint = { phones: true }

return <div>{items.length > 0 && !!breakpoint.phones && <span />}</div>
}
`,
errors: [{
message: 'Potential leaked value that might cause unintentionally rendered values or rendering crashes',
line: 6,
column: 24,
}],
},
]),
});