Skip to content

Commit 6e6e2db

Browse files
committed
Enforce JSX inline conditional as a ternary
1 parent aac7fb9 commit 6e6e2db

File tree

4 files changed

+169
-9
lines changed

4 files changed

+169
-9
lines changed

docs/rules/jsx-inline-conditional.md

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Enforce JSX inline conditional as a ternary (react/jsx-inline-conditional)
2+
3+
This rule helps avoid common rendering bugs where the left side of an inline conditional is falsy (e.g. zero) and renders the value of the condition (e.g. `0`) instead of nothing. See the note in the [official React docs](https://reactjs.org/docs/conditional-rendering.html#inline-if-with-logical--operator).
4+
5+
**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line.
6+
Fixer will fix whitespace and tabs indentation.
7+
8+
## Rule Details
9+
10+
This rule is aimed to enforce consistent indentation style. The default style is `4 spaces`.
11+
12+
Examples of **incorrect** code for this rule:
13+
14+
```jsx
15+
<div>
16+
{someCondition && <SomeComponent />}
17+
</div>
18+
<div>
19+
{someCondition || someOtherCondition && <SomeComponent />}
20+
</div>
21+
```
22+
23+
Examples of **correct** code for this rule:
24+
25+
```jsx
26+
<div>
27+
{someCondition ? <SomeComponent /> : null}
28+
</div>
29+
// --
30+
<div>
31+
{someCondition || someOtherCondition ? <SomeComponent /> : null}
32+
</div>
33+
```

index.js

+5-9
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const allRules = {
3030
'jsx-handler-names': require('./lib/rules/jsx-handler-names'),
3131
'jsx-indent': require('./lib/rules/jsx-indent'),
3232
'jsx-indent-props': require('./lib/rules/jsx-indent-props'),
33+
'jsx-inline-conditional': require('./lib/rules/jsx-inline-conditional'),
3334
'jsx-key': require('./lib/rules/jsx-key'),
3435
'jsx-max-depth': require('./lib/rules/jsx-max-depth'),
3536
'jsx-max-props-per-line': require('./lib/rules/jsx-max-props-per-line'),
@@ -124,16 +125,15 @@ module.exports = {
124125
rules: allRules,
125126
configs: {
126127
recommended: {
127-
plugins: [
128-
'react',
129-
],
128+
plugins: ['react'],
130129
parserOptions: {
131130
ecmaFeatures: {
132131
jsx: true,
133132
},
134133
},
135134
rules: {
136135
'react/display-name': 2,
136+
'react/jsx-inline-conditional': 2,
137137
'react/jsx-key': 2,
138138
'react/jsx-no-comment-textnodes': 2,
139139
'react/jsx-no-duplicate-props': 2,
@@ -158,9 +158,7 @@ module.exports = {
158158
},
159159
},
160160
all: {
161-
plugins: [
162-
'react',
163-
],
161+
plugins: ['react'],
164162
parserOptions: {
165163
ecmaFeatures: {
166164
jsx: true,
@@ -169,9 +167,7 @@ module.exports = {
169167
rules: activeRulesConfig,
170168
},
171169
'jsx-runtime': {
172-
plugins: [
173-
'react',
174-
],
170+
plugins: ['react'],
175171
parserOptions: {
176172
ecmaFeatures: {
177173
jsx: true,

lib/rules/jsx-inline-conditional.js

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* @fileoverview Enforce JSX inline conditional as a ternary
3+
* @author Kevin Ingersoll
4+
*/
5+
6+
'use strict';
7+
8+
const docsUrl = require('../util/docsUrl');
9+
10+
// ------------------------------------------------------------------------------
11+
// Rule Definition
12+
// ------------------------------------------------------------------------------
13+
14+
const messages = {
15+
inlineConditional: 'Conditional rendering in JSX should use a full ternary expression to avoid unintentionally rendering falsy values (i.e. zero)',
16+
};
17+
18+
module.exports = {
19+
meta: {
20+
docs: {
21+
description: 'Enforce JSX inline conditional as a ternary',
22+
category: 'Possible Errors',
23+
recommended: true,
24+
url: docsUrl('jsx-inline-conditional'),
25+
},
26+
fixable: 'code',
27+
messages,
28+
schema: [],
29+
},
30+
31+
create(context) {
32+
const sourceCode = context.getSourceCode();
33+
34+
return {
35+
JSXExpressionContainer(node) {
36+
if (
37+
node.expression.type === 'LogicalExpression'
38+
&& node.expression.operator === '&&'
39+
&& node.expression.right.type === 'JSXElement'
40+
) {
41+
context.report({
42+
node,
43+
messageId: 'inlineConditional',
44+
fix: (fixer) => fixer.replaceText(
45+
node,
46+
`{${sourceCode.getText(
47+
node.expression.left
48+
)} ? ${sourceCode.getText(node.expression.right)} : null}`
49+
),
50+
});
51+
}
52+
},
53+
};
54+
},
55+
};
+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* @fileoverview Enforce JSX inline conditional as a ternary
3+
* @author Kevin Ingersoll
4+
*/
5+
6+
'use strict';
7+
8+
// ------------------------------------------------------------------------------
9+
// Requirements
10+
// ------------------------------------------------------------------------------
11+
12+
const RuleTester = require('eslint').RuleTester;
13+
const rule = require('../../../lib/rules/jsx-inline-conditional');
14+
15+
const parsers = require('../../helpers/parsers');
16+
17+
const parserOptions = {
18+
ecmaVersion: 2018,
19+
sourceType: 'module',
20+
ecmaFeatures: {
21+
jsx: true,
22+
},
23+
};
24+
25+
// ------------------------------------------------------------------------------
26+
// Tests
27+
// ------------------------------------------------------------------------------
28+
29+
const ruleTester = new RuleTester({ parserOptions });
30+
ruleTester.run('jsx-inline-conditional', rule, {
31+
valid: parsers.all([
32+
{ code: '<div>{someCondition ? <div></div> : null}</div>' },
33+
{ code: '<div>{someCondition ? <SomeComponent /> : null}</div>' },
34+
{
35+
code: '<div>{someCondition ? <div>{anotherCondition ? <SomeComponent /> : null}</div> : null}</div>',
36+
},
37+
{
38+
code: '<div>{someCondition && someOtherCondition ? <SomeComponent /> : null}</div>',
39+
},
40+
{
41+
code: '<div>{possiblyNull ?? <SomeComponent />}</div>',
42+
parserOptions: {
43+
ecmaVersion: 2020,
44+
},
45+
},
46+
{
47+
code: '<div>{possiblyNull ?? <SomeComponent />}</div>',
48+
parser: parsers.TYPESCRIPT_ESLINT,
49+
},
50+
{
51+
code: '<div>{possiblyNull ?? <SomeComponent />}</div>',
52+
parser: parsers['@TYPESCRIPT_ESLINT'],
53+
},
54+
]),
55+
invalid: parsers.all([
56+
{
57+
code: '<div>{someCondition && <SomeComponent />}</div>',
58+
output: '<div>{someCondition ? <SomeComponent /> : null}</div>',
59+
errors: [
60+
{
61+
messageId: 'inlineConditional',
62+
},
63+
],
64+
},
65+
{
66+
code: '<div>{someCondition && someOtherCondition && <SomeComponent />}</div>',
67+
output:
68+
'<div>{someCondition && someOtherCondition ? <SomeComponent /> : null}</div>',
69+
errors: [
70+
{
71+
messageId: 'inlineConditional',
72+
},
73+
],
74+
},
75+
]),
76+
});

0 commit comments

Comments
 (0)