Skip to content

Commit

Permalink
Enforce JSX inline conditional as a ternary
Browse files Browse the repository at this point in the history
  • Loading branch information
holic committed Jul 2, 2022
1 parent aac7fb9 commit 6e6e2db
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 9 deletions.
33 changes: 33 additions & 0 deletions 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
<div>
{someCondition && <SomeComponent />}
</div>
<div>
{someCondition || someOtherCondition && <SomeComponent />}
</div>
```

Examples of **correct** code for this rule:

```jsx
<div>
{someCondition ? <SomeComponent /> : null}
</div>
// --
<div>
{someCondition || someOtherCondition ? <SomeComponent /> : null}
</div>
```
14 changes: 5 additions & 9 deletions index.js
Expand Up @@ -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'),
Expand Down Expand Up @@ -124,16 +125,15 @@ module.exports = {
rules: allRules,
configs: {
recommended: {
plugins: [
'react',
],
plugins: ['react'],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
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,
Expand All @@ -158,9 +158,7 @@ module.exports = {
},
},
all: {
plugins: [
'react',
],
plugins: ['react'],
parserOptions: {
ecmaFeatures: {
jsx: true,
Expand All @@ -169,9 +167,7 @@ module.exports = {
rules: activeRulesConfig,
},
'jsx-runtime': {
plugins: [
'react',
],
plugins: ['react'],
parserOptions: {
ecmaFeatures: {
jsx: true,
Expand Down
55 changes: 55 additions & 0 deletions 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}`
),
});
}
},
};
},
};
76 changes: 76 additions & 0 deletions 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: '<div>{someCondition ? <div></div> : null}</div>' },
{ code: '<div>{someCondition ? <SomeComponent /> : null}</div>' },
{
code: '<div>{someCondition ? <div>{anotherCondition ? <SomeComponent /> : null}</div> : null}</div>',
},
{
code: '<div>{someCondition && someOtherCondition ? <SomeComponent /> : null}</div>',
},
{
code: '<div>{possiblyNull ?? <SomeComponent />}</div>',
parserOptions: {
ecmaVersion: 2020,
},
},
{
code: '<div>{possiblyNull ?? <SomeComponent />}</div>',
parser: parsers.TYPESCRIPT_ESLINT,
},
{
code: '<div>{possiblyNull ?? <SomeComponent />}</div>',
parser: parsers['@TYPESCRIPT_ESLINT'],
},
]),
invalid: parsers.all([
{
code: '<div>{someCondition && <SomeComponent />}</div>',
output: '<div>{someCondition ? <SomeComponent /> : null}</div>',
errors: [
{
messageId: 'inlineConditional',
},
],
},
{
code: '<div>{someCondition && someOtherCondition && <SomeComponent />}</div>',
output:
'<div>{someCondition && someOtherCondition ? <SomeComponent /> : null}</div>',
errors: [
{
messageId: 'inlineConditional',
},
],
},
]),
});

0 comments on commit 6e6e2db

Please sign in to comment.