diff --git a/docs/rules/jsx-inline-conditional.md b/docs/rules/jsx-inline-conditional.md
new file mode 100644
index 0000000000..a2ca940807
--- /dev/null
+++ b/docs/rules/jsx-inline-conditional.md
@@ -0,0 +1,33 @@
+# Enforce JSX inline conditional as a ternary (react/jsx-inline-conditional)
+
+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).
+
+**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line.
+Fixer will fix whitespace and tabs indentation.
+
+## Rule Details
+
+This rule is aimed to enforce consistent indentation style. The default style is `4 spaces`.
+
+Examples of **incorrect** code for this rule:
+
+```jsx
+
+ {someCondition && }
+
+
+ {someCondition || someOtherCondition && }
+
+```
+
+Examples of **correct** code for this rule:
+
+```jsx
+
+ {someCondition ? : null}
+
+// --
+
+ {someCondition || someOtherCondition ? : null}
+
+```
diff --git a/index.js b/index.js
index be3c94a992..cd43d09150 100644
--- a/index.js
+++ b/index.js
@@ -30,6 +30,7 @@ const allRules = {
'jsx-handler-names': require('./lib/rules/jsx-handler-names'),
'jsx-indent': require('./lib/rules/jsx-indent'),
'jsx-indent-props': require('./lib/rules/jsx-indent-props'),
+ 'jsx-inline-conditional': require('./lib/rules/jsx-inline-conditional'),
'jsx-key': require('./lib/rules/jsx-key'),
'jsx-max-depth': require('./lib/rules/jsx-max-depth'),
'jsx-max-props-per-line': require('./lib/rules/jsx-max-props-per-line'),
@@ -124,9 +125,7 @@ module.exports = {
rules: allRules,
configs: {
recommended: {
- plugins: [
- 'react',
- ],
+ plugins: ['react'],
parserOptions: {
ecmaFeatures: {
jsx: true,
@@ -134,6 +133,7 @@ module.exports = {
},
rules: {
'react/display-name': 2,
+ 'react/jsx-inline-conditional': 2,
'react/jsx-key': 2,
'react/jsx-no-comment-textnodes': 2,
'react/jsx-no-duplicate-props': 2,
@@ -158,9 +158,7 @@ module.exports = {
},
},
all: {
- plugins: [
- 'react',
- ],
+ plugins: ['react'],
parserOptions: {
ecmaFeatures: {
jsx: true,
@@ -169,9 +167,7 @@ module.exports = {
rules: activeRulesConfig,
},
'jsx-runtime': {
- plugins: [
- 'react',
- ],
+ plugins: ['react'],
parserOptions: {
ecmaFeatures: {
jsx: true,
diff --git a/lib/rules/jsx-inline-conditional.js b/lib/rules/jsx-inline-conditional.js
new file mode 100644
index 0000000000..4e17939a90
--- /dev/null
+++ b/lib/rules/jsx-inline-conditional.js
@@ -0,0 +1,55 @@
+/**
+ * @fileoverview Enforce JSX inline conditional as a ternary
+ * @author Kevin Ingersoll
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+ inlineConditional: 'Conditional rendering in JSX should use a full ternary expression to avoid unintentionally rendering falsy values (i.e. zero)',
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ description: 'Enforce JSX inline conditional as a ternary',
+ category: 'Possible Errors',
+ recommended: true,
+ url: docsUrl('jsx-inline-conditional'),
+ },
+ fixable: 'code',
+ messages,
+ schema: [],
+ },
+
+ create(context) {
+ const sourceCode = context.getSourceCode();
+
+ return {
+ JSXExpressionContainer(node) {
+ if (
+ node.expression.type === 'LogicalExpression'
+ && node.expression.operator === '&&'
+ && node.expression.right.type === 'JSXElement'
+ ) {
+ context.report({
+ node,
+ messageId: 'inlineConditional',
+ fix: (fixer) => fixer.replaceText(
+ node,
+ `{${sourceCode.getText(
+ node.expression.left
+ )} ? ${sourceCode.getText(node.expression.right)} : null}`
+ ),
+ });
+ }
+ },
+ };
+ },
+};
diff --git a/tests/lib/rules/jsx-inline-conditional.js b/tests/lib/rules/jsx-inline-conditional.js
new file mode 100644
index 0000000000..d2563de52e
--- /dev/null
+++ b/tests/lib/rules/jsx-inline-conditional.js
@@ -0,0 +1,76 @@
+/**
+ * @fileoverview Enforce JSX inline conditional as a ternary
+ * @author Kevin Ingersoll
+ */
+
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const RuleTester = require('eslint').RuleTester;
+const rule = require('../../../lib/rules/jsx-inline-conditional');
+
+const parsers = require('../../helpers/parsers');
+
+const parserOptions = {
+ ecmaVersion: 2018,
+ sourceType: 'module',
+ ecmaFeatures: {
+ jsx: true,
+ },
+};
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({ parserOptions });
+ruleTester.run('jsx-inline-conditional', rule, {
+ valid: parsers.all([
+ { code: '' },
+ { code: '{someCondition ? : null}
' },
+ {
+ code: '{someCondition ?
{anotherCondition ? : null}
: null}
',
+ },
+ {
+ code: '{someCondition && someOtherCondition ? : null}
',
+ },
+ {
+ code: '{possiblyNull ?? }
',
+ parserOptions: {
+ ecmaVersion: 2020,
+ },
+ },
+ {
+ code: '{possiblyNull ?? }
',
+ parser: parsers.TYPESCRIPT_ESLINT,
+ },
+ {
+ code: '{possiblyNull ?? }
',
+ parser: parsers['@TYPESCRIPT_ESLINT'],
+ },
+ ]),
+ invalid: parsers.all([
+ {
+ code: '{someCondition && }
',
+ output: '{someCondition ? : null}
',
+ errors: [
+ {
+ messageId: 'inlineConditional',
+ },
+ ],
+ },
+ {
+ code: '{someCondition && someOtherCondition && }
',
+ output:
+ '{someCondition && someOtherCondition ? : null}
',
+ errors: [
+ {
+ messageId: 'inlineConditional',
+ },
+ ],
+ },
+ ]),
+});