Skip to content

Commit 9ed3411

Browse files
aleclarsonljharb
authored andcommitted
[New] jsx-no-leaked-render: add ignoreAttributes option
When true, validation of JSX attribute values is skipped.
1 parent 417e1ca commit 9ed3411

File tree

3 files changed

+85
-1
lines changed

3 files changed

+85
-1
lines changed

Diff for: docs/rules/jsx-no-leaked-render.md

+4
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ const Component = ({ elements }) => {
151151

152152
The supported options are:
153153

154+
### `ignoreAttributes`
155+
156+
*TODO*
157+
154158
### `validStrategies`
155159

156160
An array containing `"coerce"`, `"ternary"`, or both (default: `["ternary", "coerce"]`) - Decide which strategies are considered valid to prevent leaked renders (at least 1 is required). The "coerce" option will transform the conditional of the JSX expression to a boolean. The "ternary" option transforms the binary expression into a ternary expression returning `null` for falsy values. The first option from the array will be the strategy used when autofixing, so the order of the values matters.

Diff for: lib/rules/jsx-no-leaked-render.js

+25
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,21 @@ function extractExpressionBetweenLogicalAnds(node) {
5555
);
5656
}
5757

58+
const stopTypes = {
59+
__proto__: null,
60+
JSXElement: true,
61+
JSXFragment: true,
62+
};
63+
64+
function isWithinAttribute(node) {
65+
let parent = node.parent;
66+
while (!stopTypes[parent.type]) {
67+
if (parent.type === 'JSXAttribute') return true;
68+
parent = parent.parent;
69+
}
70+
return false;
71+
}
72+
5873
function ruleFixer(context, fixStrategy, fixer, reportedNode, leftNode, rightNode) {
5974
const rightSideText = getText(context, rightNode);
6075

@@ -140,6 +155,10 @@ module.exports = {
140155
uniqueItems: true,
141156
default: DEFAULT_VALID_STRATEGIES,
142157
},
158+
ignoreAttributes: {
159+
type: 'boolean',
160+
default: false,
161+
},
143162
},
144163
additionalProperties: false,
145164
},
@@ -153,6 +172,9 @@ module.exports = {
153172

154173
return {
155174
'JSXExpressionContainer > LogicalExpression[operator="&&"]'(node) {
175+
if (config.ignoreAttributes && isWithinAttribute(node)) {
176+
return;
177+
}
156178
const leftSide = node.left;
157179

158180
const isCoerceValidLeftSide = COERCE_VALID_LEFT_SIDE_EXPRESSIONS
@@ -189,6 +211,9 @@ module.exports = {
189211
if (validStrategies.has(TERNARY_STRATEGY)) {
190212
return;
191213
}
214+
if (config.ignoreAttributes && isWithinAttribute(node)) {
215+
return;
216+
}
192217

193218
const isValidTernaryAlternate = TERNARY_INVALID_ALTERNATE_VALUES.indexOf(node.alternate.value) === -1;
194219
const isJSXElementAlternate = node.alternate.type === 'JSXElement';

Diff for: tests/lib/rules/jsx-no-leaked-render.js

+56-1
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,16 @@ ruleTester.run('jsx-no-leaked-render', rule, {
213213
`,
214214
options: [{ validStrategies: ['coerce'] }],
215215
},
216+
217+
// See #3292
218+
{
219+
code: `
220+
const Component = ({ enabled, checked }) => {
221+
return <CheckBox checked={enabled && checked} />
222+
}
223+
`,
224+
options: [{ ignoreAttributes: true }],
225+
},
216226
]) || [],
217227

218228
invalid: parsers.all([].concat(
@@ -885,6 +895,25 @@ ruleTester.run('jsx-no-leaked-render', rule, {
885895
column: 24,
886896
}],
887897
},
898+
899+
// See #3292
900+
{
901+
code: `
902+
const Component = ({ enabled, checked }) => {
903+
return <CheckBox checked={enabled && checked} />
904+
}
905+
`,
906+
output: `
907+
const Component = ({ enabled, checked }) => {
908+
return <CheckBox checked={enabled ? checked : null} />
909+
}
910+
`,
911+
errors: [{
912+
message: 'Potential leaked value that might cause unintentionally rendered values or rendering crashes',
913+
line: 3,
914+
column: 37,
915+
}],
916+
},
888917
{
889918
code: `
890919
const MyComponent = () => {
@@ -1010,6 +1039,32 @@ ruleTester.run('jsx-no-leaked-render', rule, {
10101039
line: 4,
10111040
column: 33,
10121041
}],
1013-
}
1042+
},
1043+
{
1044+
code: `
1045+
const Component = ({ enabled }) => {
1046+
return (
1047+
<Foo bar={
1048+
<Something>{enabled && <MuchWow />}</Something>
1049+
} />
1050+
)
1051+
}
1052+
`,
1053+
output: `
1054+
const Component = ({ enabled }) => {
1055+
return (
1056+
<Foo bar={
1057+
<Something>{enabled ? <MuchWow /> : null}</Something>
1058+
} />
1059+
)
1060+
}
1061+
`,
1062+
options: [{ ignoreAttributes: true }],
1063+
errors: [{
1064+
message: 'Potential leaked value that might cause unintentionally rendered values or rendering crashes',
1065+
line: 5,
1066+
column: 27,
1067+
}],
1068+
},
10141069
)),
10151070
});

0 commit comments

Comments
 (0)